一、全称: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过期时间
}

 

posted on 2020-05-22 14:01  焚音留香  阅读(136)  评论(0编辑  收藏  举报