drf—jwt认证
1、jwt认证
1.1 jwt认证原理
1、用户第一次登陆时,登陆成功后,服务器通过token签发算法,生成token字符串并发送给客户端
2、客户端登陆成功后,收到响应的token,保存在cookie中
3、客户端再发起非登陆请求,将token携带发送给服务器
4、服务器接收请求,判断为非登陆请求,开始调用token校验算法校验token,校验通过即正常处理请求,否则拒绝请求
优点
1、不用在数据库存储用户登陆状态,减少数据库的写操作,大大提高性能
2、校验token时,在token字符串正确情况下,才会查询数据库,校验用户,只有数据库的读操作,可以异步进行
3、校验算法,可以非常容易的实现服务器的同步,即利于负载均衡架构的服务端
1.2 drf—jwt签发算法:
1、头字符串
- 数据来源:包含一些版本信息、公司名称等公开信息的json字典
- 算法:使用base64,根据json字典生成一个json字符串
2、载荷字符串
- 数据来源:包含用户名、设备id、登陆时间等客户端状态的隐秘信息的json字典
- 算法:使用base64,根据json字典生成一个json字符串
3、签名字符串
- 数据来源:头字符串,载荷字符串
- 不可逆的hs256算法,根据前两个字符串再加服务端的唯一安全码,生成一个随机字符串
由这三个字符串,组成token字符串
jwt校验算法:
1、服务端拿到三段式token
2、按.
切分字符串,获取头、载荷、签名字符串
3、由头、载荷字符串和服务端的唯一安全码,按照签名算法生成签名字符串
4、比较生成的字符串与客户端发来的签名字符串
5、校验通过,解析载荷字符串,校验用户信息(是否存在该用户)
5、载荷校验通过,即token校验通过
2、配置使用jwt
1、下载jwt第三方库
在github上搜索jwt即可,选取python的rest_framework版本即可
2、配置
跟drf的认证组件的配置相同
1、全局配置:
# 一般配置为全局,因为除了登陆请求,都需要认证token
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
]
2、局部配置:
# 一般不配置为局部,只在登陆视图中,通过局部配置的方式,禁用token的认证
# 局部禁用认证
authentication_classes = []
# 局部启用配置,需要先导入要配置的类,此处以jwt为例
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
authentication_classes = [JSONWebTokenAuthentication]
3、示例:多方式登陆认证
# view.py
class LoginAPIView(APIView):
'''
1 token只能由登陆接口 签发
2 登陆接口也是APIView的子类,一定会进行 认证、权限组件的校验
3 但是登陆接口不能参与任何认证或者权限的校验,无论时默认配置、全局配置或者局部配置
结论:在登陆视图中,局部禁用认证组件和权限组件
'''
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializer = serializers.LoginModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
return response.APIResponse(results={'username':serializer.data.get("username"),'token':serializer.content.get('token')})
# 认证过程,在序列化过程中进行
# serializers.py
class LoginModelSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=16, min_length=3)
password = serializers.CharField(max_length=16, min_length=3)
class Meta:
model = models.User
fields = ['username', 'password']
def validate(self, attrs):
user = self._validated_user(attrs)
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
print(token)
self.content = {'user':user,
'token':token}
return attrs
def _validated_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
# 使用正则匹配username,从而区分登陆方式
if re.match('.*@.*', username): # 邮箱
user = models.User.objects.filter(email=username).first()
elif re.match('1[3-9][0-9]{9}$', username): # 电话号码
user = models.User.objects.filter(mobile=username).first()
else: # 用户名
user = models.User.objects.filter(username=username).first()
if not user or not user.check_password(password):
raise serializers.ValidationError({'message': '用户信息异常'})
return user