一、全称:Json Web Token。
二、原理
1、三段式组成:头、体、签名(head.payload.sgin)。
2、三段信息加密前都是json格式字符串。
2、头和体都一般采用base64可逆算法加密,用于信息的存取。
3、签名一般采用md5不可逆算法加密,保证了整个token信息的安全性。
4、头中一般包含,项目信息,签名的加密方式等,相同固定的信息。例如,{"project": "项目1", "type": "md5",}。
5、体中一般包含,用户唯一标识信息,用户端设备信息,过期时间等,每个用户独特的信息。例如,{"id": "001", "over_time": "60*60*24*14",}。
6、签名是将,头和体的加密结果加上安全码组合,再整体进行加密。例如,{"head": "xxxxxx", "payload": "yyyyyy", "secret_key": "zzzzzz"}。
三、校验步骤
1、将token字符串拆成三段。
2、第一段因为一般是固定的信息,所以通常不做任何处理。
3、第二段反解出字典格式,从中通过用户唯一标识信息定位登录用户,设备信息确保登录来源可靠,过期时间确保登录时效。
4、将第一段和第二段字符串加上安全码,再组合,用同一方式加密与第三段字符串对比校验,若通过,则认为该登录用户为合法对象。
四、drf项目的jwt认证流程
1、当客户端访问登录接口,登录通过后调用token签发算法,得到token信息返回给客户端,客户端自己保存于本地,比如浏览器的cookies中。
2、校验token信息的逻辑应该写在认证器类中,在需要认证的视图中通过配置了认证器校验执行token认证。
3、token认证通过后,则会将此用户注入request.user中,后续视图代码中则可以调用到登录用户信息了。
4、若全局配置了认证器类和权限类,应至少在登录接口禁用。
五、安装jwt:pip install djangorestframework-jwt。
六、快速使用
1、需要模块:rest_framework_jwt.views.ObtainJSONWebToken。
2、代码:
urlpatterns = [ ... path('login/', ObtainJSONWebToken.as_view()) ]
七、通过jwt控制登录访问限制
1、登录才能访问:
class UserAPIView(APIView): # rest_framework.permissions.IsAuthenticated permission_classes = [IsAuthenticated,] # 先权限认证,再jwt认证 # rest_framework_jwt.authentication.JSONWebTokenAuthentication authentication_classes = [JSONWebTokenAuthentication,] def get(self,request,*args,**kwargs): ... return Response('xxx')
2、无需登录即可访问:
class AnonAPIView(APIView): authentication_classes = [JSONWebTokenAuthentication,] # 直接jwt认证,无需登录 def get(self,request,*args,**kwargs): return Response('xxx')
3、控制登录接口返回包含token信息数据的格式:
①自己写登录接口。
②使用内置的快速接口,但是重写rest_framework_jwt.utils.jwt_response_payload_handler方法,控制返回数据的格式。
八、自定义基于jwt的权限器类
# rest_framework_jwt.authentication.BaseJSONWebTokenAuthentication class NewJwtAuthentication(BaseJSONWebTokenAuthentication): def authenticate(self, request): jwt_value=request.META.get('HTTP_AUTHORIZATION') # 尝试获取token信息 if jwt_value: # 携带token的情况 try: # rest_framework_jwt.utils.jwt_decode_handler payload=jwt_decode_handler(jwt_value) # 用jwt_decode_handler方法同时校验和解码,取出payload # python内置模块jwt,可以区别jwt具体异常 except jwt.ExpiredSignature: # rest_framework.exceptions.AuthenticationFailed raise AuthenticationFailed('签名过期') except jwt.InvalidTokenError: raise AuthenticationFailed('用户非法') # 其他异常 except Exception as e: raise AuthenticationFailed(str(e)) # 可以从payload中取出通过校验的user对象 user=self.authenticate_credentials(payload) # 通过authenticate方法返回的第一个对象会注入request.user,后续即可取出使用了 return user,jwt_value # 没有token信息,直接抛出异常 raise AuthenticationFailed('您没有携带认证信息')
九、手动签发token
1、作用:实现可以,用户名/手机号/邮箱等多方式登录。
2、序列化器类:
# from rest_framework.serializers class LoginModelSerializer(serializers.ModelSerializer): # 重写username,否则会因为是post方式,而被数据库的unique约束抛出异常 username=serializers.CharField() class Meta: model=models.User # 此处的username是序列化器中重新定义的 fields=['username','password'] # 在序列化器的全局钩子中进行登录校验并签发token def validate(self, attrs): # 取出前端post过来的username,有三种可能的形式 username=attrs.get('username') password=attrs.get('password') # 通过正则匹配,确定具体形式,再去比对相应的字段 # python内置的re模块 if re.match('^1[3-9][0-9]{9}$',username): # 手机号匹配式 user=models.User.objects.filter(mobile=username).first() elif re.match('^.+@.+$',username): # 简易的邮箱匹配式 user=models.User.objects.filter(email=username).first() else: # 其他情况都认为是按照用户名登录 user=models.User.objects.filter(username=username).first() if user: # 比对通过,可得到user对象 if user.check_password(password): # 因为是密文,所以要用check_password校验密码 # rest_framework_jwt.utils.jwt_payload_handler payload = jwt_payload_handler(user) # 把user传入,得到payload # rest_framework_jwt.utils.jwt_encode_handler token = jwt_encode_handler(payload) # 把payload传入,得到token # 通过序列化器的context属性携带token,再传递到视图 self.context['token']=token # context还可以携带其他信息 self.context['username']=user.username return attrs else: # rest_framework.exceptions.ValidationError raise ValidationError('密码错误') else: raise ValidationError('用户不存在')
3、视图类:
class LoginView(ViewSet): def login(self, request, *args, **kwargs): # 注入登录信息进行序列化器校验,同时可以通过context注入其他信息,得到序列化器对象 login_ser = serializer.LoginModelSerializer(data=request.data,context={'request':request}) login_ser.is_valid(raise_exception=True) # 因为序列化器类中签发token给了context,所以此处可以取出token token=login_ser.context.get('token') return Response({'status':100,'msg':'登录成功','token':token,'username':login_ser.context.get('username')})
十、jwt的参数配置
JWT_AUTH = { # 重写内置的jwt_response_payload_handler,控制登录接口返回的数据格式 'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.limiter.new_jwt_response_payload_handler', # python内置的datetime模块 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 自定义token过期时间 }