drf-认证、权限、频率组件

目录

-认证组件

--认证简介

--使用方法

--源码分析

--使用

---局部使用

---全局使用

---局部禁用

-权限组件

--使用方法

--源码分析

--使用

---局部使用

---全局使用

---局部禁用

--内置权限校验

-频率组件

--内置频率类校验使用

---局部使用

---全局使用

--自定义频率


-认证组件

--认证简介

只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录,就不能查看,这时候需要用到认证组件

--使用方法

在app下新建一个.py文件,在其中写一个认证类(必须继承BaseAuthentication),在该类中重写authenticate方法,认证逻辑都写在其中。若认证通过,返回两个值;认证失败,抛出异常APIException或AuthenticationFailed(继承的是APIException,一般都用它)。

--源码分析

APIView-->dispatch()方法-->request = self.initialize_request(request, *args, **kwargs)-->
initialize_request()返回Request()对象,里面的authenticators从get_authenticators()中得到-->
get_authenticators(),返回return [auth() for auth in self.authentication_classes],列表生成
式,从配置的authentication_classes中一个个取类加括号生成对象-->authentication_classes在APIView
类最初配置的authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES-->搞清楚
authenticators从哪来的后-->再看dispatch方法中的关键-->self.initial(request, *args, **kwargs)
-->initial()中有三大组件——认证、权限、频率:
    # 三大组件-认证 校验用户:游客、合法用户、非法用户
    # 游客:代表校验通过,直接进入下一步校验(权限校验)
    # 合法用户:代表校验通过,将用户存在request.user中,再进入下一步校验(权限校验)
    # 非法用户:代表校验失败,抛出异常,返回403forbidden
    self.perform_authentication(request)
    # 三大组件-权限 校验用户权限:必须登录、所有用户、登录读写游客只读、自定义用户角色
    # 认证通过:可以进入下一步校验
    # 认证失败:抛出异常403
    self.check_permissions(request)
    # 三大组件-频率 限制视图接口被访问的频率数:限制条件(ip,id)、频率周期时间(s,m,h)、频率次数(几次/s)
    # 没有达到限次:正常访问接口
    # 达到限次:限制时间内不能访问,限制时间后可以访问
    self.check_throttles(request)
-->先看认证perform_authentication()-->只有一句话request.user-->此user来自drf的Request,应去
drf的Request类中找user属性/方法-->点击Ctrl+user去drf的Request-->self._authenticate()

核心

def _authenticate(self):
    for authenticator in self.authenticators:
        # authenticators是可迭代对象,是一个列表,里面是配置的认证类加括号产生的对象
        # 拿到一个个认证器,进行认证
        try:
            # 认证器调用认证方法authenticate(自己的认证类重写的)
            # 该方法应返回一个包含user,auth的元组
            # 该方法被try包裹,代表该方法会抛异常,抛异常即为认证失败
            user_auth_tuple = authenticator.authenticate(self) #self是request对象的
            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()

--使用

'''首先新建一个用户表,用于校验登录信息。这自己写了个token,并单独建了一个和User一对一关联的UserToken表'''
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type = models.IntegerField(choices=((1,'超级用户'), (2,'普通用户'), (3,'游客')))
class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to='User', on_delete=models.CASCADE)
models.py
'''我在app下新建了user_auth.py文件,里面写认证类'''
from rest_framework.authentication import BaseAuthentication
from app01 import models
from rest_framework.exceptions import AuthenticationFailed
 
class MyAuthentication1(BaseAuthentication):
    # 重写authenticate方法
    def authenticate(self, request):
        token = request.GET.get('token') # token一般放在浏览器中,这为了演示方便把它放在url?后
        if token:
            user_token_obj = models.UserToken.objects.filter(token=token).first()
            if user_token_obj:
                # 认证通过 返回两个值
                return user_token_obj.user, token
            else:
                # 认证失败 抛异常
                raise AuthenticationFailed('认证失败')
        else:
            raise AuthenticationFailed('请求中未携带token认证信息!')
auth_user.py
# 登录视图类
from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import models
import uuid
class LoginAPIView(APIView):
    # 登录发的是post请求
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = models.User.objects.filter(username=username,password=password).first()
        if user_obj:
            # 登录成功, 生成一个随机字符串
            token = uuid.uuid4() # 生成随机token,利用uuid模块,几乎不会重复
            # 将token存到UserToken表中
            # 此方法每次登录都会存一个token,既耗表资源也不恰当
            # models.UserToken.objects.create(token=token, user=user_obj)
            # 我们需要的是一个用户就一行表资源,每次登录将token更新即可,
            # 而一次也未登录的应该生成token存在UserToken表中
            models.UserToken.objects.update_or_create(defaults={'token':token}, user=user_obj)
            # update_or_create(self, defaults=None, **kwargs)使用给定的kwargs查找一个对象,如果对象存在,则用默认值更新它,否则创建一个新对象。
            return Response({'status':100,'msg':'登录成功','token':token})
        else:
            return Response({'status':101,'msg':'用户名或密码错误'})
views.py

认证类使用顺序:先用视图类中的验证类,再用settings里配置的验证类,最后用默认的验证类 

---局部使用

局部使用,只需要在视图类里加入:authentication_classes = [MyAuthentication1, ]

from rest_framework.viewsets import ModelViewSet
from app01.user_auth import MyAuthentication1
class PublishModelViewSet(ModelViewSet):
    authentication_classes = [MyAuthentication1]
    queryset = models.Publish.objects
    serializer_class = PublishSerializers
'''
此时get请求访问所有:
    http: //127.0.0.1:8000/publishes/?token=12a3bb54-fe89-4451-ae1a-b3891659b523
'''

---全局使用

'''所有视图类都认证,去setting.py中配置:'''
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':['app01.user_auth.MyAuthentication1',]
} # 是列表,可多个

---局部禁用

'''对配了全局的又想某些视图类不用,则在该视图类中重赋值为空'''
authentication_classes = []

-权限组件

之前的auth模块里自带权限校验is_staff(),这学的是drf的我们自己写的权限校验,但其实drf也有对应方法支持之前的auth模块带的权限校验,后面会有

--使用方法

在.py文件中新建一个类,继承BasePermission,重写has_permissioin方法,如果权限通过返回True;权限不通过返回False。

--源码分析

APIView-->dispatch()方法-->self.initial(request, *args, **kwargs)-->
self.check_permissions(request)-->def check_permissions(self, request)
-----核心:
    def check_permissions(self, request):
        # 遍历权限对象列表得到一个个权限对象(权限器),进行权限认证
        for permission in self.get_permissions():
            # 权限类一定得有个has_permission方法,用来写权限认证逻辑
            # 参数:权限对象self,请求对象request,视图类对象view
            # 返回值:有权限True,无权限False
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )

--使用

from rest_framework.permissions import BasePermission
class MyPermission1(BasePermission):
    # 重写has_permission方法
    def has_permission(self, request, view):
        # 是超级用户才能访问指定内容
        user_obj = request.user # 由于已经认证过了,所以request中有user
        print(user_obj.get_user_type_display()) # 显示choices字段对应的中文信息:对象.get_字段名_display()
        if user_obj.user_type == 1:
            return True
        else:
            return False
user_permission.py

model.py沿用认证里的

path('permit1/', views.PublishAPIViewPermissionTest1.as_view()),
path('permit2/', views.PublishAPIViewPermissionTest2.as_view()),
urls.py

权限类使用顺序:先用视图类中的权限类,再用settings里配置的权限类,最后用默认的权限类 

---局部使用

from app01 import user_permission
class PublishAPIViewPermissionTest1(APIView):
    authentication_classes = [MyAuthentication1]
    permission_classes = [user_permission.MyPermission1]
    def get(self, request):
        return Response('权限测试,只有超级用户才能看到哦~')
class PublishAPIViewPermissionTest2(APIView):
    authentication_classes = [MyAuthentication1]
    def get(self, request):
        return Response('权限测试,普通用户也能看到哦~')
局部使用

---全局使用

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES':['app01.user_permission.MyPermission1'], # 权限
} # 注意:在REST_FRAMEWORK里面配置就行了,不能命两个!!!

---局部禁用

permission_classes = []

--内置权限校验

drf内置的权限校验只能是通过admin中设置的用户,因为它用了IsAdminUser等它内置的

# 内置权限IsAdminUser演示
 
# 1、创建超级管理员createsuperuser
# 2、写一个测试视图类
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication
class TestView(APIView):
    authentication_classes = [SessionAuthentication, ]
    permission_classes = [IsAdminUser]
    def get(self, request):
        return Response('superuser可看')
# 3、超级用户登录admin,再访问test就有权限
# 4、正常用户没有权限看
IsAdminUser演示

-频率组件

频率即控制某个用户访问某个url的次数,如一分钟内只能三次等

drf内置的频率校验只能操作到admin用户或通过admin后台管理设置的用户

--内置频率类校验使用

drf自带的内置频率校验类有匿名用户类(未登录用户)AnonRateThrottle,登录用户类UserRateThrottle。

---局部使用

在视图类下配置throttle_classes = [AnonRateThrottle] # 内置匿名用户限制
在settings.py中的REST_FRAMEWORK中写'DEFAULT_THROTTLE_RATES':{'anon':'3/m'} 
    # 限制频率次数,1min(h-hour,y-year…)内只能3次,anon与AnonRateThrottle对应;user与UserRateThrottle对应
局部使用

---全局使用

在settings.py中配两个参数
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSER':['rest_framework.throttling_UserRateThrottle'],
        'DEFAULT_THROTTLE_RATES':{'user':'10/m'} # 必带
    }
全局使用

--自定义频率

自定义频率校验实现逻辑如下,就是要重写allow_request()和wait()两个方法,继不继承BaseThrottle都行因为Python实行的是鸭子类型(我有鸭子的两个方法特征了,我就是鸭子了),但实现的较low,3/60写的死不动态,可查看SimpleRateThrottle类的源码学习思维

'''自定义的逻辑'''
# (1)取出访问者ip
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
eg:
class MyThrottles():
    VISIT_RECORD = {}
    def __init__(self):
        self.history=None
    def allow_request(self,request, view):
        #(1)取出访问者ip
        # print(request.META)
        ip=request.META.get('REMOTE_ADDR')
        import time
        ctime=time.time()
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip]=[ctime,]
            return True
        self.history=self.VISIT_RECORD.get(ip)
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        while self.history and ctime-self.history[-1]>60:
            self.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(self.history)<3:
            self.history.insert(0,ctime)
            return True
        else:
            return False
    def wait(self):
        import time
        ctime=time.time()
        return 60-(ctime-self.history[-1])
自定义频率

 --源码分析

class SimpleRateThrottle(BaseThrottle):
    # 咱自己写的放在了全局变量,他的在django的缓存中
    cache = default_cache
    # 获取当前时间,跟咱写的一样
    timer = time.time
    # 做了一个字符串格式化,
    cache_format = 'throttle_%(scope)s_%(ident)s'
    scope = None
    # 从配置文件中取DEFAULT_THROTTLE_RATES,所以咱配置文件中应该配置,否则报错
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    def __init__(self):
        if not getattr(self, 'rate', None):
            # 从配置文件中找出scope配置的名字对应的值,比如咱写的‘3/m’,他取出来
            self.rate = self.get_rate()
        #     解析'3/m',解析成 3      m
        self.num_requests, self.duration = self.parse_rate(self.rate)
    # 这个方法需要重写
    def get_cache_key(self, request, view):
        """
        Should return a unique cache-key which can be used for throttling.
        Must be overridden.

        May return `None` if the request should not be throttled.
        """
        raise NotImplementedError('.get_cache_key() must be overridden')
    
    def get_rate(self):
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            # 获取在setting里配置的字典中的之,self.scope是 咱写的luffy
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
    # 解析 3/m这种传参
    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        # 只取了第一位,也就是 3/mimmmmmmm也是代表一分钟
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)
    # 逻辑跟咱自定义的相同
    def allow_request(self, request, view):
        """
        Implement the check to see if the request should be throttled.

        On success calls `throttle_success`.
        On failure calls `throttle_failure`.
        """
        if self.rate is None:
            return True

        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])
        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()
    # 成功返回true,并且插入到缓存中
    def throttle_success(self):
        """
        Inserts the current request's timestamp along with the key
        into the cache.
        """
        self.history.insert(0, self.now)
        self.cache.set(self.key, self.history, self.duration)
        return True
    # 失败返回false
    def throttle_failure(self):
        """
        Called when a request to the API has failed due to throttling.
        """
        return False

    def wait(self):
        """
        Returns the recommended next request time in seconds.
        """
        if self.history:
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            remaining_duration = self.duration

        available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)
SimpleRateThrottle源码分析

 

 


 

posted @ 2022-12-17 13:39  weer-wmq  阅读(29)  评论(0编辑  收藏  举报