前言
我为什么要写这篇文章?
最近由于工作需要,在原来的项目(Django,B/S)的基础上需要增加和客户端(Client)的通信。这个时候session机制就不合适了,需要用到token。我选择了Rest Framework 插件来完成这个功能。关于django token的教程,网上一搜一大堆,我为什么还要写?主要由以下几点:
饮水思源:写这篇文章之前,我被各种bug折磨了一天,借鉴了两位大佬的东西,欢迎去他们下面查看原文。
https://www.jianshu.com/p/e0a206212df4
https://www.jianshu.com/p/078fb116236e
- 网上文章虽然多,但多数是大神的教程,神龙见首不见尾,原理一讲,一段关键代码贴上来就完事了,对我这样的学渣实在是不怎么友好。
- 做个学习记录,给后来者一个方便
开始
整个过程总共分为以下几步
- 创建一个 Django项目,新建一个app(废话)
- 安装 django-rest-framework (还是废话)
- 在settings中注册rest-framework,配置 REST_FRAMEWORK
- 编写自己的验证方法,并加入REST_FRAMEWORK配置
- 在登陆视图中创建 Token
- 在操作视图中添加 Toekn 验证
- 使用postman 验证
创建Django项目
我相信搜索到这篇文章的都不是0基础,起码创建项目,新增一个app是没问题的吧。我就略过这步了。如果有需要指导创建项目,新增app的,请留言!
安装 REST_FRAMEWORK
> pip install django-rest-framework
注册 rest-framework并配置
不需要导入这个包,直接注册即可
INSTALLED_APPS = [
...
'rest_framework.authtoken'
...
]
配置一下, 这个是必须的,不然返回数据的时候回报错
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
}
编写我们的验证方法
在app下面创建一个名为auth.py的文件,将下面的内容写入这个文件中
import datetime
from django.utils.translation import ugettext_lazy
from django.core.cache import cache
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from rest_framework.authtoken.models import Token
from rest_framework import HTTP_HEADER_ENCODING
# 获取请求头信息
def get_authorization_header(request):
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, type('')):
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
# 自定义认证方式,这个是后面要添加到设置文件的
class ExpiringTokenAuthentication(BaseAuthentication):
model = Token
def authenticate(self, request):
auth = get_authorization_header(request)
if not auth:
return None
try:
token = auth.decode()
except UnicodeError:
msg = ugettext_lazy("无效的Token, Token头不应包含无效字符")
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(token)
def authenticate_credentials(self, key):
# 尝试从缓存获取用户信息(设置中配置了缓存的可以添加,不加也不影响正常功能)
token_cache = 'token_' + key
cache_user = cache.get(token_cache)
if cache_user:
return cache_user, cache_user # 这里需要返回一个列表或元组,原因不详
# 缓存获取到此为止
# 下面开始获取请求信息进行验证
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed("认证失败")
if not token.user.is_active:
raise exceptions.AuthenticationFailed("用户被禁用")
# Token有效期时间判断(注意时间时区问题)
# 我在设置里面设置了时区 USE_TZ = False,如果使用utc这里需要改变。
if (datetime.datetime.now() - token.created) > datetime.timedelta(hours=0.1*1):
raise exceptions.AuthenticationFailed('认证信息已过期')
# 加入缓存增加查询速度,下面和上面是配套的,上面没有从缓存中读取,这里就不用保存到缓存中了
if token:
token_cache = 'token_' + key
cache.set(token_cache, token.user, 600)
# 返回用户信息
return token.user, token
def authenticate_header(self, request):
return 'Token'
将这个验证方法注册到配置里面
REST_FRAMEWORK = {
# 新增的
'DEFAULT_AUTHENTICATION_CLASSES': (
'apps.xtauth.auth.ExpiringTokenAuthentication', # 根据自己的实际情况填写路径
),
# 下面是刚刚已经写好的
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
}
一切准备工作就绪,下面就开始使用它了。
创建Token
创建token首先要有用户,我这里直接使用系统的User表,就不直接创建了
下面的代码写在视图views.py
from django.http import JsonResponse
from django.shortcuts import HttpResponse
from rest_framework.decorators import api_view
from rest_framework.authtoken.models import Token
from django.contrib import auth
@api_view(['POST'])
def login(request):
receive = request.data
username = receive.get('username')
password = receive.get('password')
user = auth.authenticate(username=username, password=password)
if not user:
return HttpResponse("用户名和密码不匹配")
# 校验通过
# 删除原有的Token
old_token = Token.objects.filter(user=user)
old_token.delete()
# 创建新的Token
token = Token.objects.create(user=user)
return JsonResponse({"username": user.username, "token": token.key})
说明一下:
- 装饰器api.view是rest framework提供的,它可以指定请求方法,如果这里使用了它,那么针对这个方法的CSRF就会失效,所以不需要再额外添加 csrf_exempt 装饰器
- 这个装饰器会给request对象添加一个data属性,post上传的数据都可以在这里面直接获取,示例中用户名和密码就是直接从里面取出来的,上传的json数据已经转换好了,直接获取即可。
- auth.authenticate() 方法是Django提供的验证用户名和密码是否匹配的,和rest framework毛关系都没有
- 创建token是通过Token.objects.create() 创建的。创建之前最好先删除原来的token,否则一个用户多次创建token会报错的,数据库唯一约束
验证Token
创建好了之后,自然就该验证了,写一个什么函数验证一下
@api_view(['POST'])
def do_something(request):
receive = request.data
print(receive)
if request.user.is_authenticated: # 验证Token是否正确
print("Do something...")
return JsonResponse({"msg": "验证通过"})
else:
print("验证失败")
return JsonResponse({"msg": "验证失败"})
说明:
- request.user.is_authenticated:这个是验证Token是否正确的。user对象也是使用了api_view装饰器之后添加的。
- 很明显,rest framework, 我们应该遵循RestFul规范,返回json格式数据才是中规中矩的。如果起前面的设置中REST_FRAMEWORK下面没有配置 rest_framework.renderers.JSONRenderer 的话,这里会报错,一定要添加
路由配置
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.login),
path('doSomething/', views.do_something),
]
很简单,就是简单的登陆和操作验证两个url
Postman测试
功能都已经完成了,就是这么简单,最后我们来测试一下
先创建一个用户:
用户名:zhangsan 密码: 123456
然后使用Postman登陆获取Token
可以看到,一切都那么自然,成功获取到了Token
接下来就来测试一下这个token的正确性
![过期的情况](https://i-blog.csdnimg.cn/blog_migrate/354e8af69d03289338f0b309cb8a8789.png)
由于之前时间设置得太短,导致写完文字后token已经失效了,尴尬。来个正确的打开方式
![成功](https://i-blog.csdnimg.cn/blog_migrate/70dcf7b9f37d33bc9bd96ab0713354c7.png)
注意几点:
- headers里面的key只能是【Authorization】,别的名称将会验证失败,至于能不能,应该是可以的,只是我不知道(手动狗头)
- token一定要在有效期内才能验证通过(废话),时间在auth.py文件里面设置,注意时区的问题。自己试验,建议设置USE_TZ = False,简单粗暴。
OK,大功告成,希望这篇文章能够帮你,欢迎留言回复。