• JWT


    一、全称: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过期时间
    }
  • 相关阅读:
    .dll .h .lib等文件的作用与区别
    [转自]语言黑客的福音
    [转载]一个台湾程序员的心历路程
    Servlet学习总结
    会话跟踪(Cookie & Session)
    JSP学习总结
    人往高处走,水往低处流
    GDI 和GDI+ 混合编程
    常用到的知识
    Sqlite3相关
  • 原文地址:https://www.cnblogs.com/caoyu080202201/p/13303284.html
Copyright © 2020-2023  润新知