drf认证权限频率

认证的使用

# 使用流程
1 写一个类,继承BaseAuthentication,重写authenticate,写认证的逻辑;
2 认证通过,返回两个值,一个值最终给了Requet对象的user;
3 认证失败,抛异常:APIException或者AuthenticationFailed

# 使用范围
	全局使用,项目的配置文件
	局部使用,写在视图类中

认证的配置

# 全局配置,项目的settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.app_auth.LoginAuthentication", ]
}

# 局部配置,当前视图类使用
authentication_classes = [LoginAuthentication, ]

# 局部禁用
authentication_classes = []

# 可以有多个认证,按照列表中的顺序以此执行

代码实现

# app_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models

class LoginAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')	# token放在请求头里面,变形了[坑]
        token_obj = models.UserToken.objects.filter(token=token).first()
        if token_obj:
            # 返回两个值分别赋值给request.user,request.auth
            return token_obj.user, token 
        else:
            # 认证失败,抛异常
            raise AuthenticationFailed('token校验失败')

# views.py
class LoginAPIView(APIView):
	# 局部禁用,登陆功能
    authentication_classes = []

    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:
            # uuid值保存数据库
            token_id = uuid.uuid4()
            models.UserToken.objects.update_or_create(defaults={'token': token_id}, user=user_obj)
            back_info_obj = utils.BackInfo().add_param('token', token_id)
            return Response(back_info_obj.results)
        else:
            back_info_obj = utils.BackInfo().add_error(code=101, message='用户名或密码错误')
            return Response(back_info_obj.results)

认证源码阅读

# APIView的dispatch方法,替换了原生django的当次请求对象
# 替换的过程是实例化一个drf的Request对象,并给该request对象传入一个存放认证器类对象的列表
# dispatch方法内调用initial方法,在initial方法内调用登陆认证方法perform_authentication()

# 在该登陆认证方法内,调用request.user,[user是request对象的方法伪装成了数据属性]
# request的user内调用reuest的_authenticate方法是登陆认证的核心所在

# 在reuest的_authenticate方法内,循环request对象保存的认证器对象列表,以此校验
# 循环过程中,调用认证器对象的authenticate方法,并把request传进去。
# 这是就开始执行我们自己写的认证类对象的authenticate方法,
# 在authenticate方法内,认证成功,返回两个值;认证失败,抛出AuthenticationFailed异常

# 认证成功,authenticate方法需要返回两个数据分别赋值给request.user和request.auth

# 因此,认证通过进入视图后,可以在request中取出当前登陆用户对象request.user

核心源码

# request对象的_authenticate方法内
def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        依次调用每一个认证类对象的authenticate方法,并传入request对象
    	self.user, self.auth = user_auth_tuple 这是为啥认证成功要返回两个值的原因
        """
        for authenticator in self.authenticators:
            try:
                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()

权限

权限源码分析

# APIView---->dispatch---->initial--->self.check_permissions(request)(APIView的对象方法)
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)
            )

权限的使用

# 写一个类,继承BasePermission,重写has_permission,如果权限通过,就返回True,不通过就返回False
from rest_framework.permissions import BasePermission

class UserPermission(BasePermission):
    def  has_permission(self, request, view):
        try:
            # 由于认证已经过了,request内就有user对象了,当前登录用户
            user = request.user  # 当前登录用户
            # 如果该字段用了choice,通过get_字段名_display()就能取出choice后面的中文
            print(user.get_user_type_display())
            if user.user_type == 1:
                return True
            else:
                return False
         except Exception as e:
            raise APIException(e)

# 全局使用, 项目配置文件
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",],
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.app_auth.UserPermission',
    ],
}      
# 局部使用,视图类中
permission_classes = [app_auth.UserPermission]

# 局部禁用
class TestView(APIView):
    permission_classes = []

补充浏览器访问,认证未通过,直接进入权限校验异常问题

# 浏览器访问接口,当自定义的认证不通过抛出异常时不会直接跳出程序,而是直接进入权限校验阶段
# 这是在权限校验阶段如果出现异常,那么就会在浏览器页面中出现不友好的报错页面
# 此时即使通过自定义drf的异常捕获,也没办法正确捕获到这个异常。

# 有两种解决办法,一种是在权限校验阶段使用if判断,尽可能的避免异常的出现
# 二种是在权限校验阶段主动在可能出现错误的位置try捕获异常,捕获到直接return False
# 这是浏览器页面上就会出现我们自定义的异常处理返回页面(和postman提交出现的返回值一致)

自定义请求方式控制权限

需求:根据用户类型,限制请求方式;如:管理员可以使用一个视图的所有功能(增删查改),普通用户仅可以查看该视图。即管理员可以提交post\delete\put\get请求,而普通用户仅可以提交get请求

from django.contrib.auth.models import AnonymousUser
from rest_framework.permissions import BasePermission

from app01 import models

class APIPermissions(BasePermission):

    def has_permission(self, request, view):
        user_obj = request.user
        if isinstance(user_obj, AnonymousUser):
            return False
        elif isinstance(user_obj, models.User):
            if user_obj.role == 1:
                # 管理员可以访问所有请求
                return True
            elif user_obj.role == 2 and request.method in ['GET', 'POST']:
                # 会员可以查看和新增数据
                return True
            elif user_obj.role == 3 and request.method == 'GET':
                # 普通用户仅能查看数据
                return True
            else:
                return False
        else:
            return False

内置权限(了解)

# 演示一下内置权限的使用:IsAdminUser,控制是否对网站后台有权限的人
# 1 创建超级管理员
# 2 写一个测试视图类
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication
class TestView3(APIView):
    authentication_classes = [SessionAuthentication,]
    permission_classes = [IsAdminUser]
    def get(self, request, *args, **kwargs):
        return Response('这是22222222测试数据,超级管理员可以看')
# 3 超级用户登录到admin,再访问test3就有权限
# 4 使用内置去权限是基于django的auth模块使用的,只有is_staff的用户才有可以登陆admin系统

# 注意:使用内置权限必须配合内置的认证器类一块使用

频率

自定义频率限制

# 根据IP限制访问频次
import time

class APIThrottles:
    VISIT_RECORD = {}  # 存放所有IP的访问记录

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        user_ip = request.META.get('REMOTE_ADDR')
        # ip不在字典中表示第一次访问,保存ip然后直接通过
        if user_ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[user_ip] = [time.time()]
            return True
        self.history = self.VISIT_RECORD.get(user_ip)
        # 删除列表中时间超过1分钟的记录, self.history = [t1, t2, t3, t4]
        # 循环操作之后列表中存放都是一分钟之内的访问记录
        # 这样做的一个目的:当用户长时间未登陆或者限流之后(1分钟之后),清空列表中的无效记录
        this_time = time.time()
        while self.history and this_time - self.history[0] >= 60:
            self.history.pop(0)

        # 列表长度小于5表示1分钟内访问次数小于5次,此时不限流;否则限流
        if len(self.history) < 5:
            self.history.append(this_time)
            return True
        else:
            return False

    def wait(self):
        throttled_time = time.time() - self.history[-1]		# 被限制之后经过的时间
        wait_time = 60 - throttled_time						# 还需等待的时间
        return wait_time

内置频率(根据IP限制)

基于IP的内置频率可以不使用配合自定义的认证和权限校验,不必配合内置的认证和权限校验一块使用。

# 继承SimpleRateThrottle, 写一个类,重写get_cache_key方法
from rest_framework.throttling import SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
    scope = 'luffy'		# 标识,限制频率的关键字
    def get_cache_key(self, request, view):
        return self.get_ident(request)			# get_ident方法内根据ip限制,通过则返回True

# 全局配置,setting里配置(一分钟访问三次)
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES':{
        'luffy':'3/m'  # key要跟类中的scop对应
}
    
# 使用
from app01.app_throttling import VisitThrottle
class BookModelViewSet(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = ser.BookSerializer
    throttle_classes = [VisitThrottle, ]
    
    
# 了解,错误信息中文显示
class Course(APIView):
    throttle_classes = [MyThrottles,]

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')
    
    def throttled(self, request, wait):		# 重写throttled方法
        from rest_framework.exceptions import Throttled
        class MyThrottled(Throttled):
            default_detail = '傻逼啊'
            extra_detail_singular = '还有 {wait} second.'
            extra_detail_plural = '出了 {wait} seconds.'
        raise MyThrottled(wait)

内置的频率限制(限制未登录用户)

基于用户的内置频率限制必须使用基于auth表的用户,因为他们都是基于django的auth组件。

下述两个内置的频率限制必须同时使用才能同时控制登陆用户的访问频率和未登陆用户的访问频率。

使用他们必须使用内置的认证校验。

# 全局使用  限制未登录用户1分钟访问5次
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '3/m',
    }
}
############## views.py
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
class TestView4(APIView):
    authentication_classes=[]
    permission_classes = []
    def get(self, request, *args, **kwargs):
        return Response('我是未登录用户')

# 局部使用
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from rest_framework.throttling import AnonRateThrottle
class TestView5(APIView):
    authentication_classes=[]
    permission_classes = []
    throttle_classes = [AnonRateThrottle]
    
    def get(self, request, *args, **kwargs):
        return Response('我是未登录用户,TestView5')

内置频率限制(限制登录用户的访问频次)

# 需求:未登录用户1分钟访问5次,登录用户一分钟访问10次
# 全局:在setting中
  'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'user': '10/m',
        'anon': '5/m',
    }
        
 # 局部配置:
	在视图类中配一个就行

posted @ 2020-07-09 22:18  the3times  阅读(194)  评论(0编辑  收藏  举报