• drf三大认证组件


    一、认证模块工作原理

    源码分析:

    • 首先:在APIView的dispatch中调用了initial方法进行了三大认证

      • 认证模块:校验用户是否登陆: 登陆用户、非法用户、游客
      • 权限模块:校验用户是否拥有权限: 校验对象是 登陆用户和游客
      • 频率模块:访问接口的次数在设定的时间范围内是否过快:
      def initial(self, request, *args, **kwargs):
          # 认证模块:校验用户是否登陆: 登陆用户、非法用户、游客
          self.perform_authentication(request)
          # 权限模块:校验用户是否拥有权限: 校验对象是 登陆用户和游客
          self.check_permissions(request)
          # 频率模块:访问接口的次数在设定的时间范围内是否过快:
          #     配置访问频率,每次访问都要缓存记次,超次后需要等待的事件
          self.check_throttles(request)
      
    • 进入self.perform_authentication(request)会发现其中只有一句话

      def perform_authentication(self, request):
          request.user
      
    • 那我们首先就要知道request是谁这个request是通过实例化request类来进行二次封装的request对象。
      这个request对象来.user实际上是调用了Request类中的user方法通过@property装饰器来装饰的数据方法属性

      @property
      def user(self):
          if not hasattr(self, '_user'):
              with wrap_attributeerrors():
                  self._authenticate()
              return self._user
      
    • 进入这个方法,会发现有一句话self._authenticate(). 就是在这个方法内找到了认证模块的配置类,并调用了该类的authenticate(self)方法,并把self传了进去。该self就是request对象,得到返回值是一个用户和认证的元祖

          def _authenticate(self):w
              for authenticator in self.authenticators:	# 重点,找到配置类,并循环
                  try:
                      # 调用改类的authenticate方法,并传参,self就是request对象,得到返回值是一个用户和认证的元祖
                      user_auth_tuple = authenticator.authenticate(self)	
                  except exceptions.APIException:
                      self._not_authenticated()
                      raise
      
                  if user_auth_tuple is not None:
                      self._authenticator = authenticator
                      self.user, self.auth = user_auth_tuple
                      return
      
              self._not_authenticated()
      
      • 进入这个方法, 会发现有一句话for authenticator in self.authenticators:,这里是重点

        • self是谁?是Request对象,它的authenticators就是在init初始化方法中赋值的
        def __init__(self, request, parsers=None, authenticators=None,
                         negotiator=None, parser_context=None):
        	self.authenticators = authenticators or ()
        
      • 那么self.authenticators赋值的内容是什么呢?就是在二次封装Request实例化对象是传进去的参数

            def initialize_request(self, request, *args, **kwargs):
                # 准备要解析的内容字典
                parser_context = self.get_parser_context(request)
                return Request(
                    request,
                    parsers=self.get_parsers(), # 解析模块,在封装request时,将数据一并解析了
                    authenticators=self.get_authenticators(),   # 认证模块
                    negotiator=self.get_content_negotiator(),
                    parser_context=parser_context
                )
        
      • 此时才知道self.get_authenticators()才是真正获取认证模块配置信息的内容,里面和之前的模块一样,导入配置

        def get_authenticators(self):
            return [auth() for auth in self.authentication_classes]
        
      • self.authentication_classes的配置信息就是全局配置的内容。因此我们也可以通过重写该属性来完成局部配置。该全局配置的默认配置内容就是:

        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        
        '''settings.py APISettings类'''
        
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.BasicAuthentication'
        ],
        
      • 发现默认的配置就是使用authentication文件下的SessionAuthentication类和BasicAuthentication

    二、认证模块使用方法

    注:认证模块默认只要有一个认证通过得到user用户就可以

    通过上面的源码分析。我们知道全局配置和局部配置的实现方法。

    全局配置:

    # drf的配置
    REST_FRAMEWORK = {
        # 认证组件的全局配置
        'DEFAULT_AUTHENTICATION_CLASSES':[
            # 'rest_framework.authentication.SessionAuthentication',
            # 'rest_framework.authentication.BasicAuthentication',
            
            # 自定义认证类
            "api.utils.authentications.TokenAuthentication",
        ],
    }
    

    局部配置:

    from api.utils import authentications
    class UserListAPIView(ListAPIView):
        # 自定义局部配置
    	authentication_classes = [authentications.TokenAuthentication]
    

    2.1 自定义认证类的实现方法

    自定义认证规则:

    请求格式:
    key: AUTHORIZATION
    value: token eyJ1c2VybmFtZSI6ICJjaGVuIn0=.eyJwayI6IDJ9.4a1884a2ac002993bbb93b68bc45fa1a
    
    • 继承BaseAuthentication类,重写authenticate方法
    • 认证规则(authenticate方法实现体):
      • 没有携带认证信息,直接返回None => 游客
      • 有认证信息,校验失败,抛异常 => 非法用户
      • 有认证信息,校验出User对象 => 合法用户
    # 自定义认证类
    from rest_framework.authentication import BaseAuthentication
        # 认证异常
    from rest_framework.exceptions import AuthenticationFailed
    """
    认证模块工作原理
    1)继承BaseAuthentication类,重写authenticate方法
    2)认证规则(authenticate方法实现体):
        没有携带认证信息,直接返回None => 游客
        有认证信息,校验失败,抛异常 => 非法用户
        有认证信息,校验出User对象 => 合法用户
    """
    from . import common
    class TokenAuthentication(BaseAuthentication):
        prefix = 'Token'
        def authenticate(self, request):
            # 通过前台的请求头来进行token认证信息校验
            auth = request.META.get('HTTP_AUTHORIZATION')
            # 没有直接返回None => 游客
            if not auth:
                return None
            auth_list = auth.split()
    
            # 有认证信息,校验失败,抛异常 => 非法用户
            if len(auth_list) != 2 or auth_list[0].lower() != self.prefix.lower():
                raise AuthenticationFailed("非法用户")
    
            # 取出token
            token = auth_list[1]
            # 校验token,失败抛异常,成功返回(user, token)
            user_obj = common._get_obj(token)
            return (user_obj, token)
    
    # 校验算法(认证类)
    """
    拆封token:一段 二段 三段
    用户名:b64decode(一段)
    用户主键:b64decode(二段)
    碰撞解密:md5(用户名+用户主键+服务器秘钥) == 三段
    """
    # 认证异常
    from rest_framework.exceptions import AuthenticationFailed
    from ..models import UserInfo
    def _get_obj(token):
        token_list = token.split(".")
        # token长度不符合
        if len(token_list) != 3:
            raise AuthenticationFailed("非法用户")
    
        username = json.loads(base64.b64decode(token_list[0])).get('username')
        pk = json.loads(base64.b64decode(token_list[1])).get('pk')
    
        md5_dic = {
            'username': username,
            'pk': pk,
            'key': settings.SECRET_KEY
        }
        print(md5_dic)
        if token_list[2] != hashlib.md5(json.dumps(md5_dic).encode()).hexdigest():
            raise AuthenticationFailed('token内容异常')
        user_obj = UserInfo.objects.get(pk=pk, username=username)
        return user_obj
    
    '''views.py'''
    
    # 查看所有用户信息,前提:必须是登录的超级管理员
    from api.utils import authentications
    from api.utils import permissions
    from rest_framework.generics import ListAPIView
    class UserInfoListAPIView(ListAPIView):
        # 同电商网站,多接口是不需要登录的,少接口需要登录,使用在需要登录的接口中完成局部配置,进行局部接口校验
        authentication_classes = [authentications.TokenAuthentication]
        permission_classes = [permissions.SuperUserPermission]
    	
        # 获取除了管理员的所有用户
        queryset = models.UserInfo.objects.filter(is_active=True, is_superuser=False)
        serializer_class = serializers.UserInfoModelSerializer
    

    2.2 通过序列化模块实现登陆,通过认证模块进行校验

    # 登录接口:如果是超级管理员登录,返回一个可以校验出超级管理员的token字符串
    # 只要有用户登录,就可以返回一个与登录用户相关的token字符串 => 返回给前台 => 签发token => user_obj -> token_str
    class LoginAPIView(APIView):
        # 登录接口一定要做:局部禁用 认证 与 权限 校验
        authentication_classes = []
        permission_classes = []
        def post(self,request,*args,**kwargs):
            ser_obj = serializers.LoginModelSerializer(data=request.data)
            ser_obj.is_valid(raise_exception=True)
            return common.APIResponse(data={
                'username': ser_obj.user.username,
                'token': ser_obj.token
            })
    
    # 用户登陆的接口
    from django.contrib import auth
    from . import common
    class LoginModelSerializer(serializers.ModelSerializer):
    
        # username和password字段默认会走系统校验,而系统的post请求校验,一定当做增方式校验,所以用户名会出现 重复 的异常
        # 所以自定义两个反序列化字段接收前台的账号密码
        user = serializers.CharField(write_only=True)
        pwd = serializers.CharField(write_only=True)
        class Meta:
            model = models.UserInfo
            fields = ["user","pwd"]
    
        def validate(self, attrs):
            user = attrs.get('user')
            pwd = attrs.get('pwd')
            try:
                user_obj = auth.authenticate(username=user, password=pwd)
            except:
                raise serializers.ValidationError({'user': '账号或密码错误'})
    
            if not user_obj:
                raise serializers.ValidationError({'user': '账号或密码错误'})
    
            # 拓展名称空间
            self.user = user_obj
            # 放token
            self.token = common._get_token(user_obj)
            return attrs
    
    # 签发算法(认证类)
    # 自定义签发token
    # 分析:拿user得到token,后期还需要通过token得到user
    # token:用户名(base64加密).用户主键(base64加密).用户名+用户主键+服务器秘钥(md5加密)
    # eg: YWJj.Ao12bd.2c953ca5144a6c0a187a264ef08e1af1
    
    # 签发算法:b64encode(用户名).b64encode(用户主键).md5(用户名+用户主键+服务器秘钥)
    """
    封装token:一段 二段 三段
    用户名:b64decode(一段)
    用户主键:b64decode(二段)
    三段加密:md5(用户名+用户主键+服务器秘钥) == 三段
    """
    from django.conf import settings
    def _get_token(obj):
        username = base64.b64encode(
            json.dumps({"username": obj.username}).encode("utf8")
        ).decode("utf8")
        pk = base64.b64encode(
            json.dumps({"pk": obj.id}).encode("utf8")
        ).decode("utf8")
        t3_json = json.dumps({
            'username': obj.username,
            'pk': obj.id,
            'key': settings.SECRET_KEY
        })
        t3 = hashlib.md5(t3_json.encode()).hexdigest()
        return '%s.%s.%s' % (username, pk, t3)
    
    

    三、权限模块使用方法

    全局配置:

    # drf的配置
    REST_FRAMEWORK = {
        # 权限模块的全局配置
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.AllowAny',
            'rest_framework.permissions.IsAuthenticated',
    
            # 自定义权限类
            "api.utils.permissions.SuperUserPermission",
        ],
    }
    
    

    局部配置:

    from api.utils import authentications
    class UserListAPIView(ListAPIView):
        # 自定义局部配置
    	permission_classes = [permissions.SuperUserPermission]
    
    

    3.1 系统的权限类

    注:配置的权限类,必须全部通过权限校验才算通过

    系统的权限类:
    AllowAny:不限制
    IsAuthenticated:必须是登录用户
    IsAdminUser:必须是后台用户
    IsAuthenticatedOrReadOnly:读操作无限制,其他操作需要登录
    
    

    3.2 自定义权限类的实现方法

    权限校验可以实现多个,必须全部通过

    • 继承BasePermission类,重写has_permission方法
    • 权限规则(has_permission方法实现体):
      • 返回True,代表有权限
      • 返回False,代表无权限
    # 自定义权限类
    from rest_framework.permissions import BasePermission
    """
    权限模块工作原理
    1)继承BasePermission类,重写has_permission方法
    2)权限规则(has_permission方法实现体):
        返回True,代表有权限
        返回False,代表无权限
    """
    class SuperUserPermission(BasePermission):
        def has_permission(self, request, view):
            print(request.user)
            print(request.auth)
            return request.user and request.user.is_superuser
    
    

    四、频率模块使用方法

    频率模块就是显示规定时间内的访问次数

    drf默认的频率配置是空,也就是说,如果需要我们就要自己去实现

    • 定义类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性
    • scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置
    • get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key

    自定义实现频率模块

    '''throttles.py'''
    
    # 自定义频率类
    from rest_framework.throttling import SimpleRateThrottle
    # 1)定义类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性
    # 2)scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置
    # 3)get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key
    
    class ThreeTimeUserThrottle(SimpleRateThrottle):
        # 对应的频率设置,会去settings.py中找相应的配置
        scope = 'three'
    	
        '''
        设置属性rate也可以直接进行频率配置
        如:rate = "3/min"
        '''
        
        # 当前用户缓存的key
        def get_cache_key(self, request, view):
            return 'throttle:user_%s' % (request.user.id)
    

    全局配置:

    # drf的配置
    REST_FRAMEWORK = {
        # 频率模块的全局配置
        'DEFAULT_THROTTLE_RATES': {
            # 自定义频率设置
            'three': '3/min',
        },
    }
    

    局部配置:

    from rest_framework_jwt import authentication
    class UserInfoListAPIView(ListAPIView):
        '''频率模块局部配置'''
        throttle_classes = [throttles.ThreeTimeUserThrottle]
    
  • 相关阅读:
    通过ifconfig命令分析
    网络协议初探
    商品详情页面属性价格显示其对应价格
    ecshop属性 {$goods.goods_attr|nl2br} 标签的赋值相关
    CI模板中如何引入模板
    jQuery取得/设置select的值
    ecshop如何增加多个产品详细描述的编辑器
    获取span里面的值(特殊情况下 )
    一个页面有相同ID元素的情况分析
    表单辅助函数-form_open()
  • 原文地址:https://www.cnblogs.com/XuChengNotes/p/11938861.html
Copyright © 2020-2023  润新知