zoukankan      html  css  js  c++  java
  • 10 drfJWT认证

    一.JWT认证

    Json Web Token(本质就是token)

    在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制。

    1.1 构成和工作原理

    • 构成

    JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

    1.1.1 header

    jwt的头部承载两部分信息:

    • 声明类型,这里是jwt
    • 声明加密的算法 通常直接使用 HMAC SHA256

    完整的头部就像下面这样的JSON:

    {
      'typ': 'JWT',
      'alg': 'HS256'
    }
    

    然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    

    1.1.2 payload

    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

    • 标准中注册的声明
    • 公共的声明
    • 私有的声明

    标准中注册的声明 (建议但不强制使用) :

    • iss: jwt签发者
    • sub: jwt所面向的用户
    • aud: 接收jwt的一方
    • exp: jwt的过期时间,这个过期时间必须要大于签发时间
    • nbf: 定义在什么时间之前,该jwt都是不可用的.
    • iat: jwt的签发时间
    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。

    公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    定义一个payload:

    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }
    

    然后将其进行base64加密,得到JWT的第二部分。

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    

    1.1.3 signature

    JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)
    • payload (base64后的)
    • secret

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

    // javascript
    var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
    
    var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

    关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。

    文档网站:http://getblimp.github.io/django-rest-framework-jwt/

    二.JWT的安装和使用

    2.1 安装

    pip install djangorestframework-jwt
    

    2.2 drf项目的jwt认证开发流程

    1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中

    2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户

    注:登录接口需要做 认证 + 权限 两个局部禁用

    2.3 简单使用:

    # 1 创建超级用户
    python3 manage.py createsuperuser
    # 2 配置路由urls.py
    from django.urls import path
    from rest_framework_jwt.views import obtain_jwt_token
    urlpatterns = [
        path('login/', obtain_jwt_token),
    ]
    # 3 postman测试
    向后端接口发送post请求,携带用户名密码,即可看到生成的token
    
    # 4 setting.py中配置认证使用jwt提供的jsonwebtoken
    # 5 postman发送访问请求(必须带jwt空格)
    
    

    2.3.1 自定义的auth认证类

    from rest_framework_jwt.authentication import BaseAuthentication,BaseJSONWebTokenAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from rest_framework_jwt.authentication import jwt_decode_handler
    from rest_framework_jwt.authentication import get_authorization_header,jwt_get_username_from_payload
    from rest_framework import exceptions
    class MyToken(BaseJSONWebTokenAuthentication):
        def authenticate(self, request):
            jwt_value=str(request.META.get('HTTP_AUTHORIZATION'))
            # 认证
            try:
                payload = jwt_decode_handler(jwt_value)
    
            except Exception:
                raise exceptions.AuthenticationFailed("认证失败")
            user=self.authenticate_credentials(payload)
            return user,None
        
    #局部使用,全局使用
    
    

    2.4 基于JWT的自定义权限类

    from rest_framework.authentication import BaseAuthentication  # 基于它
    from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication # 基于它
    from rest_framework.exceptions import AuthenticationFailed
    # from rest_framework_jwt.authentication import jwt_decode_handler
    from rest_framework_jwt.utils import jwt_decode_handler # 跟上面是一个
    import jwt
    
    
    from api import models
     class MyJwtAuthentication(BaseAuthentication):
         def authenticate(self, request):
             jwt_value=request.META.get('HTTP_AUTHORIZATION')
             if jwt_value:
                 try:
                 #jwt提供了通过三段token,取出payload的方法,并且有校验功能
                     payload=jwt_decode_handler(jwt_value)
                 except jwt.ExpiredSignature:
                     raise AuthenticationFailed('签名过期')
                 except jwt.InvalidTokenError:
                     raise AuthenticationFailed('用户非法')
                 except Exception as e:
                    # 所有异常都会走到这
                     raise AuthenticationFailed(str(e))
                 # 因为payload就是用户信息的字典
                 print(payload)
                 # return payload, jwt_value
                 # 需要得到user对象,
                 # 第一种,去数据库查
                 # user=models.User.objects.get(pk=payload.get('user_id'))
                 # 第二种不查库
                 user=models.User(id=payload.get('user_id'),username=payload.get('username'))
                 return user,jwt_value
             # 没有值,直接抛异常
             raise AuthenticationFailed('您没有携带认证信息')
    
    

    2.5 多方式登录

    ## views.py
    # 重点:自定义login,完成多方式登录
    from rest_framework.viewsets import ViewSet
    from rest_framework.response import Response
    class LoginViewSet(ViewSet):
        # 需要和mixins结合使用,继承GenericViewSet,不需要则继承ViewSet
        # 为什么继承视图集,不去继承工具视图或视图基类,因为视图集可以自定义路由映射:
        #       可以做到get映射get,get映射list,还可以做到自定义(灵活)
        def login(self, request, *args, **kwargs):
            serializer = serializers.LoginSerializer(data=request.data, context={'request': request})
            serializer.is_valid(raise_exception=True)
            token = serializer.context.get('token')
            return Response({"token": token})
          
          
          
    
    ## ser.py
    # 重点:自定义login,完成多方式登录
    class LoginSerializer(serializers.ModelSerializer):
        # 登录请求,走的是post方法,默认post方法完成的是create入库校验,所以唯一约束的字段,会进行数据库唯一校验,导致逻辑相悖
        # 需要覆盖系统字段,自定义校验规则,就可以避免完成多余的不必要校验,如唯一字段校验
        username = serializers.CharField()
        class Meta:
            model = models.User
            # 结合前台登录布局:采用账号密码登录,或手机密码登录,布局一致,所以不管账号还是手机号,都用username字段提交的
            fields = ('username', 'password')
    
        def validate(self, attrs):
            # 在全局钩子中,才能提供提供的所需数据,整体校验得到user
            # 再就可以调用签发token算法,将user信息转换为token
            # 将token存放到context属性中,传给外键视图类使用
            user = self._get_user(attrs)
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            self.context['token'] = token
            return attrs
    
        # 多方式登录
        def _get_user(self, attrs):
            username = attrs.get('username')
            password = attrs.get('password')
            import re
            if re.match(r'^1[3-9][0-9]{9}$', username):
                # 手机登录
                user = models.User.objects.filter(mobile=username, is_active=True).first()
            elif re.match(r'^.+@.+$', username):
                # 邮箱登录
                user = models.User.objects.filter(email=username, is_active=True).first()
            else:
                # 账号登录
                user = models.User.objects.filter(username=username, is_active=True).first()
            if user and user.check_password(password):
                return user
    
            raise ValidationError({'user': 'user error'})
    # utils.py
    import re
    from .models import User
    from django.contrib.auth.backends import ModelBackend
    class JWTModelBackend(ModelBackend):
        def authenticate(self, request, username=None, password=None, **kwargs):
            try:
                if re.match(r'^1[3-9]d{9}$', username):
                    user = User.objects.get(mobile=username)
                else:
                    user = User.objects.get(username=username)
            except User.DoesNotExist:
                return None
            if user.check_password(password) and self.user_can_authenticate(user):
                return user
    
    

    2.6 配置多方式登录

    settings.py
    AUTHENTICATION_BACKENDS = ['user.utils.JWTModelBackend']
    
    

    2.7 JWT的配置参数

    # jwt的配置
    import datetime
    JWT_AUTH={
        'JWT_RESPONSE_PAYLOAD_HANDLER':'app02.utils.my_jwt_response_payload_handler',
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间,手动配置
    }
    
    
  • 相关阅读:
    CSS3新特性
    CSS简述
    HTML5新属性
    Python学习笔记(十二)
    Python学习笔记(十一)
    Python学习笔记(十)
    Python学习笔记(九)
    Python学习笔记(八)
    Python学习笔记(七)
    Python学习笔记(六)
  • 原文地址:https://www.cnblogs.com/bailongcaptain/p/13307252.html
Copyright © 2011-2022 走看看