为了对移动应用有更好的支持,同时弥补cookie不能完成跨域认证的缺陷,会话技术的第三种,token,被引入。这一节我们一起来看看token的工作原理,同时实现一个简单的token生成和认证流程。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。
先总结下我的操作环境:
因为Django长期支持版本2.2LTS和前一个长期支持版本1.11LTS有许多地方不一样,需要小心区分。
先来回顾一下session认证的流程:
这样导致了两个问题:
做为以上流程的改进,token的实现有两种方式:
网上关于JWT是否取代session的讨论很多,我个人也觉得token跟session比起来有很大的优势。但是如果是网页的话还是session实现起来更简单一点,而且因为保存有用户的信息,服务端想做一些操作就非常容易,例如修改用户角色,修改session过期时间等等。这个问题见仁见智,我们这里不做深入讨论。
下面就以上面的第一种改进方法来进行一个简单的实现,了解了背后的基本逻辑,以后遇到类似JWT的框架也就很容易理解了。还是做一个简单的登录交互:
register/
页面输入用户名和密码进行注册,服务端查询数据库,不存在该用户就写入数据库否则注册失败login/
页面输入用户名和密码进行登录,服务端验证用户名和密码,都正确就记录并返回token,否则登录失败。这里简化模型,没有考虑客户端存储token,可以是cookie或者LocalStorage)homepage/
带入自己的token访问,验证token成功则显示个人主页,否则提示错误首先创建一个数据模型,存储用户姓名,密码,和token
这里只是演示,生产环境的token会存放在缓存中进行查询
class Employee(models.Model):
e_name = models.CharField(max_length=16, unique=True)
e_passwd = models.CharField(max_length=128)
e_token = models.CharField(max_length=256)
之后迁移到数据库中备用
创建路由规则和view函数如下,提供用户注册的页面
path('register/', views.register, name='register'),
def register(request):
if request.method == 'GET':
return render(request, 'token_register.html')
elif request.method == 'POST':
name = request.POST.get('name')
password = request.POST.get('password')
try:
employee = Employee(e_name=name, e_passwd=password) # test only, password should be saved in hash
employee.save()
return HttpResponse('Register Successfully')
except Exception as e:
return HttpResponse('User already exists')
其中在GET
方法下返回的h5页面如下
Register
访问http://127.0.0.1:8000/token/register/
,进行用户注册
注册成功
查看数据库中的记录发现多了一条
创建路由规则和view函数如下,提供登录的页面
path('login/', views.login, name='login'),
def login(request):
if request.method == 'GET':
return render(request, 'token_login.html')
elif request.method == 'POST':
name = request.POST.get('name')
password = request.POST.get('password')
employee = Employee.objects.filter(e_name=name).filter(e_passwd=password)
if employee.exists():
ip = request.META.get('REMOTE_ADDR')
token = generate_token(ip, name)
result = employee.first()
result.e_token = token
result.save()
data = {
'status': 200,
'token': token,
}
return JsonResponse(data=data)
else:
data = {
'status': 800,
}
return JsonResponse(data=data)
在GET
方法是返回的h5页面和注册几乎一样
Login
然后是这里是重点,就是POST
方法的逻辑。如果用户名和密码都正确,就生成该用户的token,存到数据库中的同时下发给用户。成功了返回200,失败返回自定义的800
企业开发中使用6xx-9xx来进行自定义状态码
Token在生成的时候,要针对当前时间戳单个ip的单个用户必须唯一,否则就不具备身份识别性。所以其实就是对ip,用户名,和当前时间戳的信息集合做一个哈希。 这里的token生成函数如下
def generate_token(ip, name):
timestamp = int(time.time() * 1000)
str_bfmd5 = str(timestamp) + ip + name
token = hashlib.md5(str_bfmd5.encode('utf-8')).hexdigest()
return token
这里使用的是hashlib中的md5,当然也可以用sha128,sha256等等方式。注意编码对象必须是bytes类型才可以。
用刚才注册的账号访问http://127.0.0.1:8000/token/login/
登录
成功获取到返回的Json数据
数据库中也成功进行了记录
有了token,用户在访问的时候就可以带上它,我这里采用的是url查询参数的方式进行。
创建路由和view函数如下
path('homepage/', views.homepage, name='homepage'),
def homepage(request):
token = request.GET.get('token')
employee = Employee.objects.filter(e_token=token)
if employee.exists():
return HttpResponse(employee.first().e_name)
else:
return HttpResponse('Token error')
然后带上上一步返回的token创建url
http://127.0.0.1:8000/token/homepage/?token=25ec0f4ad459c0a549fd85cf5ca879f4
成功访问个人主页
也可以直接用反向解析的方式从登录页面直接重定向到个人主页来
def login(request):
if request.method == 'GET':
return render(request, 'token_login.html')
elif request.method == 'POST':
name = request.POST.get('name')
password = request.POST.get('password')
employee = Employee.objects.filter(e_name=name).filter(e_passwd=password)
if employee.exists():
ip = request.META.get('REMOTE_ADDR')
token = generate_token(ip, name)
result = employee.first()
result.e_token = token
result.save()
# data = {
# 'status': 200,
# 'token': token,
# }
# return JsonResponse(data=data)
return HttpResponseRedirect(reverse('token:homepage')+'?token='+token)
else:
data = {
'status': 800,
}
return JsonResponse(data=data)
因为对于下发的token没有办法控制,退出登陆的时候只需要删除数据库里面和token对应的记录即可。
到了这里,MTV模型的基础就了解的差不多了。从下一节开始我们一起来看看一些进阶的内容