zoukankan      html  css  js  c++  java
  • DRF ---- 三大认证 认证/权限/频率 自定义

    三大认证

    三大认证源码入口 restframework 框架内的 dispatch 方法:

    try: 
        # 三大认证 (认证, 权限 , 评率) , 用来替换csrf安全认证, 要比csrf认证强大的多
        self.initial(request, *args, **kwargs)
    

    点进去 initial 里面的最下面有三个方法:

     # 认证组件
     self.perform_authentication(request)
     # 权限组件
     self.check_permissions(request)
     # 评率组件
     self.check_throttles(request)
    

    perform_authentication(认证组件)

      校验用户 : 游客 合法用户 非法用户

      游客: 代表校验通过 进入下一步校验 (权限校验)

      合法用户: 代表校验通过 将用户 存储在 request.user 中 再进行下一步校验

      非法用户: 代表校验失败 抛出异常 返回403 权限异常

    check_permissions(权限组件)

      权限组件: 校验用户权限 -必须登录 所有用户 登录读写 游客只读 自定义用户角色

      认证通过: 可以进行下一步的校验

      认证失败: 抛出异常 返回403权限异常结果

    check_throttles(评率组件)

    ​  频率组件: 限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)

      没有达到限次:正常访问接口

      达到限次: 限制时间内不能访问, 限制时间到达后,可以重新访问

    RBAC(基于用户权限访问控制的认证)

      (自己了解:基于auth的认证规则)
      Django框架采用的是RBAC认证规则,RBAC认证规则通常会分为 三表规则、五表规则,Django采用的是六表规则
      三表:用户表、角色表、权限表
      五表:用户表、角色表、权限表、用户角色关系表、角色权限关系表
      六表:用户表、角色表、权限表、用户角色关系表、角色权限关系表、用户权限关系表

    补充: django 中的 RBAC

    from django.contrib.auth.models import AbstractUser
    # 里面已经写好了所有的字段
    
    class AbstractUser(AbstractBaseUser, PermissionsMixin): # PermissionsMixin
    
    class PermissionsMixin(models.Model):
    	# 分组字段
        groups = models.ManyToManyField(
            Group,
            verbose_name=_('groups'),
            blank=True,
            related_name="user_set",
            related_query_name="user",
        )
        # 权限字段
        user_permissions = models.ManyToManyField(
            Permission,
            verbose_name=_('user permissions'),
            blank=True,
            related_name="user_set",
            related_query_name="user",
        )
        
        # 指点了反向查找的 name
        
        '''
        用户表:角色groups,权限user_permissions
    	角色表:用户user_set,权限permissions
    	权限表:用户user_set,角色group_se
        '''
    

    三表的互相查询:

    from django.test import TestCase
    
    # Create your tests here.
    import os
    import sys
    
    if __name__ == "__main__":
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "三大认证组价.settings")
        import django
        django.setup()
        from django.contrib.auth.models import AbstractUser
    
        from app01 import models
        user_obj = models.User.objects.first() # type: models.User  # type 可以设置提示
        print(user_obj.username)
        # 用户表查 权限  有多个 first() 拿第一个 .name
        print(user_obj.user_permissions.first().name)  # Can add 用户表
        # 用户表 查 角色 可以有多个 first() 拿第一个
        print(user_obj.groups.first().name)  #管理员
    
        # 权限表 查询 导入 权限表
        from django.contrib.auth.models import Permission
        permission_obj = Permission.objects.filter(pk=16).first()
        # 权限查 自己
        print(permission_obj.name) # Can change 用户表
        # 权限 查 用户
        print(permission_obj.user_set.first().username)
        # 权限 查 角色
        p_16 = Permission.objects.filter(pk=17).first()
        print(p_16.group_set.first().name)
    
        # 角色表查 自己
        from django.contrib.auth.models import Group # type: Group
        group_obj = Group.objects.first()
        print(group_obj.name)
        # 查 用户
        print(group_obj.user_set.first().username)
        # 查 权限
        print(group_obj.permissions.first().name) # Can change 用户表
    

    重点:如果自定义User表后,再另一个项目中采用原生User表,完成数据库迁移时,可能失败

    1 卸载Django重新装

    2 将django.contrib下面的admin、auth下的数据库迁移记录文件清空

    自定义user表

    认证组件

        def perform_authentication(self, request):
            request.user
            # 直接使用request.user 点方法 点的是方法属性
    

    Request 位置 restframework下的request.py 下的Request

        @property
        def user(self):
            # 判断如果没有_user 直接调用_authenticate
            if not hasattr(self, '_user'):
                with wrap_attributeerrors():
                    self._authenticate()
            # 否则返回 _user
            return self._user
    

    所有 他做认证应该是在_authenticate内做的

    Request类的 方法属性 user 的get方法 => self._authenticate() 完成认证
        
        认证的细则:
        # 做认证
        def _authenticate(self):
            # 遍历拿到一个个认证器,进行认证
            # self.authenticators配置的一堆认证类产生的认证类对象组成的 list
            for authenticator in self.authenticators:
                try:
                    # 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象)
                    # 返回值:登陆的用户与认证的信息组成的 tuple
                    # 该方法被try包裹,代表该方法会抛异常,抛异常就代表认证失败
                    user_auth_tuple = authenticator.authenticate(self)
                except exceptions.APIException:
                    self._not_authenticated()
                    raise
    
                # 返回值的处理
                if user_auth_tuple is not None:
                    self._authenticator = authenticator
                    # 如果有返回值,就将 登陆用户 与 登陆认证 分别保存到 request.user、request.auth
                    self.user, self.auth = user_auth_tuple
                    return
            # 如果返回值user_auth_tuple为空,代表认证通过,但是没有 登陆用户 与 登陆认证信息,代表游客
            self._not_authenticated()
    

    其中 循环的 authenticators 是self.的 所以 应该是初始化的二次封装封装的!

     return Request(authenticators=self.get_authenticators(),) # 二次封装request的时候添加的
    

    get_authenticators()

        def get_authenticators(self):
            return [auth() for auth in self.authentication_classes]
        	# 配置的认证类
    
    'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.BasicAuthentication'
        ],
    
     from rest_framework.authentication import SessionAuthentication
     from rest_framework.authentication import BaseAuthentication
    

    他们都继承了 BaseAuthentication 且重写的 authenticate 方法

    class SessionAuthentication(BaseAuthentication):
        def authenticate(self, request):
            user = getattr(request._request, 'user', None)
            # 判断如果用户没认证 返回none 
            if not user or not user.is_active:
                return None
    		# 禁用csrf
            self.enforce_csrf(request)
            # 用户认证了 返回元祖
            return (user, None)
    

    自定义认证

    1) 创建继承BaseAuthentication的认证类
    2) 实现authenticate方法
    3) 实现体根据认证规则 确定游客、非法用户、合法用户
    4) 进行全局或局部配置
    

    认证规则

    i.没有认证信息返回None(游客)
    ii.有认证信息认证失败抛异常(非法用户) AuthenticationFailed
    iii.有认证信息认证成功返回用户与认证信息元组(合法用户)
    

    代码

    # 自定义认证类
    # 1)继承BaseAuthentication类
    # 2)重新authenticate(self, request)方法,自定义认证规则
    # 3)认证规则基于的条件:
    #       没有认证信息返回None(游客)
    #       有认证信息认证失败抛异常(非法用户)
    #       有认证信息认证成功返回用户与认证信息元组(合法用户)
    # 4)完成视图类的全局(settings文件中)或局部(确切的视图类)配置
    from rest_framework.authentication import BaseAuthentication
    # 抛异常
    from rest_framework.exceptions import AuthenticationFailed
    from . import models
    class MyAuthentication(BaseAuthentication):
        """
        同前台请求头拿认证信息auth(获取认证的字段要与前台约定)
        没有auth是游客,返回None
        有auth进行校验
            失败是非法用户,抛出异常
            成功是合法用户,返回 (用户, 认证信息)
        """
        def authenticate(self, request):
            # 前台在请求头携带认证信息,
            #       且默认规范用 Authorization 字段携带认证信息,
            #       后台固定在请求对象的META字段中 HTTP_AUTHORIZATION 获取
            auth = request.META.get('HTTP_AUTHORIZATION', None)
    
            # 处理游客
            if auth is None:
                return None
    
            # 设置一下认证字段小规则(两段式):"auth 认证字符串"
            auth_list = auth.split()
    
            # 校验合法还是非法用户
            if not (len(auth_list) == 2 and auth_list[0].lower() == 'auth'):
                raise AuthenticationFailed('认证信息有误,非法用户')
    
            # 合法的用户还需要从auth_list[1]中解析出来
            # 注:假设一种情况,信息为abc.123.xyz,就可以解析出admin用户;实际开发,该逻辑一定是校验用户的正常逻辑
            if auth_list[1] != 'abc.123.xyz':  # 校验失败
                raise AuthenticationFailed('用户校验失败,非法用户')
    
            user = models.User.objects.filter(username='admin').first()
    
            if not user:
                raise AuthenticationFailed('用户数据有误,非法用户')
            return (user, None)
    
    authentication_classes = [] # 局部配置
    
    'DEFAULT_AUTHENTICATION_CLASSES': [] # 全局配置
    

    权限校验

    self.check_permissions(request) # 入口
    
        认证细则:
        def check_permissions(self, request):
            # 遍历权限对象列表得到一个个权限对象(权限器),进行权限认证
            for permission in self.get_permissions():
                # 权限类一定有一个 has_permission 权限方法,用来做权限认证的
                # 参数:权限对象self、请求对象request、视图类对象
                # 返回值:有权限返回True,无权限返回False
                if not permission.has_permission(request, self):
                    self.permission_denied(
                        request, message=getattr(permission, 'message', None)
                    )
    

    配置

        # 全局
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.AllowAny',
        ]
            
        # 局部 
        permission_classes = []
    

    这里有4个默认给的权限校验类

    AllowAny

    # 认证规则全部返还True:return True
    # 游客与登陆用户都有所有权限
    

    IsAuthenticated

    # 认证规则必须有登陆的合法用户:return bool(request.user and request.user.is_authenticated)
    # 游客没有任何权限,登陆用户才有权限
    

    IsAdminUser

    # 认证规则必须是后台管理用户:return bool(request.user and request.user.is_staff)
    # 游客没有任何权限,登陆用户才有权限
    

    IsAuthenticatedOrReadOnly

        # 认证规则必须是只读请求或是合法用户:
            return bool(
                request.method in SAFE_METHODS or
                request.user and
                request.user.is_authenticated
            )
            # 游客只读,合法用户无限制
    

    使用:

    # api/views.py
    from rest_framework.permissions import IsAuthenticated
    class TestAuthenticatedAPIView(APIView):
        permission_classes = [IsAuthenticated]
        def get(self, request, *args, **kwargs):
            return APIResponse(0, 'test 登录才能访问的接口 ok')
    
    # 因为默认全局配置的权限类是AllowAny
    # settings.py
    REST_FRAMEWORK = {
        # 权限类配置
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.AllowAny',
        ],
    }
    

    自定义权限类

    1) 创建继承BasePermission的权限类
    2) 实现has_permission方法
    3) 实现体根据权限规则 确定有无权限
    4) 进行全局或局部配置
    

    认证规则

    i.满足设置的用户条件,代表有权限,返回True
    ii.不满足设置的用户条件,代表有权限,返回False
    
    # utils/permissions.py
    from rest_framework.permissions import BasePermission
    from django.contrib.auth.models import Group
    class MyPermission(BasePermission):
        def has_permission(self, request, view):
            # 只读接口判断
            r1 = request.method in ('GET', 'HEAD', 'OPTIONS')
            # group为有权限的分组
            group = Group.objects.filter(name='管理员').first()
            # groups为当前用户所属的所有分组
            groups = request.user.groups.all()
            r2 = group and groups
            r3 = group in groups
            # 读接口大家都有权限,写接口必须为指定分组下的登陆用户
            return r1 or (r2 and r3)
        
        
    # 游客只读,登录用户只读,只有登录用户属于 管理员 分组,才可以增删改
    from utils.permissions import MyPermission
    class TestAdminOrReadOnlyAPIView(APIView):
        permission_classes = [MyPermission]
        # 所有用户都可以访问
        def get(self, request, *args, **kwargs):
            return APIResponse(0, '自定义读 OK')
        # 必须是 自定义“管理员”分组 下的用户
        def post(self, request, *args, **kwargs):
            return APIResponse(0, '自定义写 OK')
    

    频率组件

    # 评率组件
    self.check_throttles(request)
    
    def check_throttles(self, request):
        throttle_durations = []
        # 1)遍历配置的频率认证类,初始化得到一个个频率认证类对象(会调用频率认证类的 __init__() 方法)
        # 2)频率认证类对象调用 allow_request 方法,判断是否限次(没有限次可访问,限次不可访问)
        # 3)频率认证类对象在限次后,调用 wait 方法,获取还需等待多长时间可以进行下一次访问
        # 注:频率认证类都是继承 SimpleRateThrottle 类
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                # 只要频率限制了,allow_request 返回False了,才会调用wait
                throttle_durations.append(throttle.wait())
    
                if throttle_durations:
                    durations = [
                        duration for duration in throttle_durations
                        if duration is not None
                    ]
    
                    duration = max(durations, default=None)
                    self.throttled(request, duration)
    

    自定义频率类

    1. 自定义一个继承 SimpleRateThrottle 类 的频率类 --- rate 比率

    2. 设置一个 scope 类属性,属性值为任意见名知意的字符串 ---scope 范围

    3. 在settings配置文件中,配置drf的DEFAULT_THROTTLE_RATES,格式为 {scope字符串: '次数/时间'} (“s”,“秒”,“m”,“最小值”,“h”,“小时”,“d”,“天”)

    4. 在自定义频率类中重写 get_cache_key 方法 ----cache 高速缓存

    限制的对象返回 与限制信息有关的字符串

    不限制的对象返回 None (只能放回None,不能是False或是''等)

    案例短信接口 3 / min 频率限制

    # utils.throttles.py
    from rest_framework.throttling import SimpleRateThrottle
    
    class SMSRateThrottle(SimpleRateThrottle):
        scope = 'sms'
    
        # 只对提交手机号的get方法进行限制
        def get_cache_key(self, request, view):
            mobile = request.query_params.get('mobile')
            # 没有手机号,就不做频率限制
            if not mobile:
                return None
            # 返回可以根据手机号动态变化,且不易重复的字符串,作为操作缓存的key
            return 'throttle_%(scope)s_%(ident)s' % {'scope': self.scope, 'ident': mobile}
    
    # drf配置
    REST_FRAMEWORK = {
        # 频率限制条件配置
        'DEFAULT_THROTTLE_RATES': {
            'sms': '1/min'
        },
    
    # 视图
    from .throttles import SMSRateThrottle
    class TestSMSAPIView(APIView):
        # 局部配置频率认证
        throttle_classes = [SMSRateThrottle]
        def get(self, request, *args, **kwargs):
            return APIResponse(0, 'get 获取验证码 OK')
        def post(self, request, *args, **kwargs):
            return APIResponse(0, 'post 获取验证码  OK')
    
  • 相关阅读:
    ICONS-图标库
    图形资源
    vue项目中,如果修改了组件名称,vscode编辑器会在引入修改组件的名字处提示红色波浪线 The file is in the program because:Imported via xxx Root file specified for compilation .
    接口在dev环境报跨域问题(has been blocked by CORS policy:Response to preflight request doesn't pass access control check:No 'Access-Control-Allow-Origin' header ispresent on the requested resource.),qa环境正常
    阿里云occ的图片文件URL用浏览器直接打开无法访问,提示This XML file does noe appear to have any style information associated with it. The document tree is shown below.
    vue 项目使用element ui 中tree组件 check-strictly 用法(父子不互相关联的反显情况)
    高德地图进行线路规划绘制标记点操作(vue)
    vue中实现拖拽调整顺序功能
    2021-01-22 浏览器相关知识
    2021-01-22 js 相关知识点
  • 原文地址:https://www.cnblogs.com/lddragon1/p/12158476.html
Copyright © 2011-2022 走看看