权限类与频率类

133 阅读8分钟

权限类的使用

# 认证: 检验用户是否登录,只有登录的用户才能使用
# 权限: 用户登录之后,某个接口只有超级管理员才能访问,普通用户不能访问

# 权限类使用步骤:
    -写一个类,继承BasePermission
    -重写has_permission方法
    -在方法中校验用户是否有权限(来到权限类说明用户已经通过了认证类,request.user就是当前用户)
    -如果有权限返回True,没有返回False
    -self.message 返回给前端提示信息
    -全局使用、局部禁用、局部使用

models.py

class UserToken(models.Model):
    user = models.OneToOneField(to='User', on_delete=models.CASCADE)
    token = models.CharField(max_length=32, null=True)

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type = models.IntegerField(choices=((1, '管理员'), (2, 'VIP'), (3, '屌丝用户')), default=3)

auth.py

from rest_framework.permissions import BasePermission

class UserTypePermissions(BasePermission):
    # 重写has_permission方法 有权限返回True 否则返回False
    def has_permission(self, request, view):
        if request.user.user_type == 1:  # 只有管理员有权限访问
            return True
        else:
            self.message = '您是%s, 没有权限访问' % request.user.get_user_type_display()
            # self.message 用来控制前端的显示信息
            # 使用了choice后,user.user_type 拿到的是数字类型,想拿到数字对应的文字信息: user.get_user_type_display()
            return False

频率类使用

# 无论是否登录和是否有权限,都要限制访问的频率,比如一分钟访问3次

# 使用步骤
    -写一个类 继承SimpleRateThrottle
    -重写get_cache_key,返回唯一的字符串,会以这个字符串做频率限制
    -写一个类属性:scope = ''  这个属性需要跟配置文件对应
    -配置文件写:
    'DEFAULT_THROTTLE_RATES': {
    'xxx': '3/m'  # 每分钟最多访问三次
 }
    -局部配置 全局配置 局部禁用

auth.py

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
class MyThrottling(SimpleRateThrottle):  # 我们继承SimpleRateThrottle去写,而不是继承BaseThrottle去写
    # 类属性,这个类属性可以随意命名,但要跟配置文件对应
    scope = 'luffy'
    def get_cache_key(self, request, view):
        # 返回什么,频率就以什么做限制
        # 可以通过用户id限制    
        # 可以通过ip地址限制   ip: request.META.get('REMOTE_ADDR')
        return request.META.get('REMOTE_ADDR')

settings.py

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES':['app01.throttling.MyThrottling'], # 全局配置接口访问频率限制
    'DEFAULT_THROTTLE_RATES': {
        'luffy': '3/m'  
    }
}

3 认证源码分析

# 写个认证类,重写某个方法,配置在视图类上,就有认证了---》认证类加了,在视图类的方法中,request.user就是当前登录用户---》猜认证类的执行,是在在视图类的方法之前执行的
# 源码分析:
    -之前咱们读APIView的执行流程---》包装了新的request,执行了3大认证,执行视图类的方法,处理了全局异常
    -入口:APIView的dispatch
    -APIView的dispatch的496行上下:self.initial(request, *args, **kwargs)
    -APIView的initial
    -413行上下:有三句话,分别是:认证,权限,频率
    	self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    -读认证类的源码---》APIView的perform_authentication(request),315行上下
    	def perform_authentication(self, request):
        	request.user  # 新的request
    -request是新的request---》Request类中找user属性(方法),是个方法包装成了数据属性
	-来到Request类中找:220def user(self):
            if not hasattr(self, '_user'): # Request类的对象中反射_user
                with wrap_attributeerrors():
                    self._authenticate()  # 第一次会走这个代码
            return self._user
    -Request的self._authenticate()---》373def _authenticate(self):
            for authenticator in self.authenticators: # 配置在视图类中所有的认证类的对象 
                try:
                    #(user_token.user, token)
                    user_auth_tuple = authenticator.authenticate(self) # 调用认证类对象的authenticate
                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()
            
            
    # 总结:认证类,要重写authenticate方法,认证通过返回两个值或None,认证不通过抛AuthenticationFailed(继承了APIException)异常

4 权限源码分析

-先读最简单的权限执行流程---》APIView的check_permissions(request),325行上下
    def check_permissions(self, request):
        for permission in self.get_permissions():
            # permission是咱们配置在视图类中权限类的对象,对象调用它的绑定方法has_permission
            # 对象调用自己的绑定方法会把自己传入(权限类的对象,request,视图类的对象)
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
                
   -APIVIew的self.get_permissions(),273行上下
	return [permission() for permission in self.permission_classes]
   -self.permission_classes 就是咱们在视图类中配的权限类的列表
   -所以这个get_permissions返回的是 咱们在视图类中配的权限类的对象列表[UserTypePermession(),]


	# 总结:权限类源码
    	-为什么要写一个类,重写has_permission方法,有三个参数,为什么一定要return TrueFalse,messgage可以做什么用

5 简单读频率类源码

# 源码分析:
    -之前咱们读APIView的执行流程---》包装了新的request,执行了3大认证,执行视图类的方法,处理了全局异常
    -入口:APIView的dispatch
    -APIView的dispatch的496行上下:self.initial(request, *args, **kwargs)
    -APIView的initial
    -413行上下:有三句话,分别是:认证,权限,频率
    	self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    -APIView的check_throttles:351上下
        def check_throttles(self, request):
            throttle_durations = []
            for throttle in self.get_throttles():
                if not throttle.allow_request(request, self):
                    throttle_durations.append(throttle.wait())
                    
                    
   -总结:要写频率类,必须重写allow_request方法,返回True(没有到频率的限制)或False(到了频率的限制)

鸭子类型

# 走路像鸭子,说话像鸭子,它就是鸭子

# 指的是面向对中,子类不需要显示的继承某个类,只要有某个的方法和属性,那我就属于这个类

# 假设有个鸭子类Duck类,有两个方法,run,speak方法
# 假设又有一个普通鸭子类,PDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,普通鸭子类的对象就是鸭子这种类型;如果不继承,普通鸭子类的对象就不是鸭子这种类型
#假设又有一个唐老鸭子类,TDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,唐老鸭子类的对象就是鸭子这种类型;如果不继承,唐老鸭子类的对象就不是鸭子这种类型

# python不推崇这个,它推崇鸭子类型,指的是
不需要显示的继承某个类,只要我的类中有run和speak方法,我就是鸭子这个类

# 有小问题:如果使用python鸭子类型的写法,如果方法写错了,它就不是这个类型了,会有问题
# python为了解决这个问题:
	-方式一:abc模块,装饰后,必须重写方法,不重写就报错
    -方式二:drf源码中使用的:父类中写这个方法,但没有具体实现,直接抛异常

作业

# 1 编写图书和出版社的5个接口,所有接口都要有一分钟访问5次的频率限制
# 2 图书的接口需要登录才能方法,出版社的接口需要登录,并且是超级用户才能访问
# 3 整理认证和频率的执行流程
---------可以不需要写----
# 3 继承BaseThrottle编写频率类
	# 自定义的逻辑  {ip:[时间1,时间2]}
    #(1)取出访问者ip
    #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
    #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
    #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
    #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
    
    
    class OurThrottling(BaseThrottle):
        def allow_request(self, request, view):
            # 自定义的逻辑  {ip:[时间1,时间2]}
            # (1)取出访问者ip
            # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
            # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
            # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
            # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
            return False

auth.py

from app01 import models
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import BasePermission
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle


class LoginAuth(BaseAuthentication):
    # 重写authenticate方法
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        # 通过token查询该token是否是在表中有记录
        user_token = models.UserToken.objects.filter(token=token).first()
        if user_token:
            return user_token.user, token
        else:
            raise AuthenticationFailed('您还没登录')


class UserTypePermissions(BasePermission):
    # 重写has_permission方法
    def has_permission(self, request, view):
        if request.user.user_type == 1:
            return True
        else:
            self.message = '您是%s, 没有权限访问' % request.user.get_user_type_display()
            return False


class TimeThrottle(SimpleRateThrottle):
    scope = 'day07_throttle'

    def get_cache_key(self, request, view):
        # 返回什么,频率就以什么做限制
        # 可以通过用户id限制
        # 可以通过ip地址限制
        return request.META.get('REMOTE_ADDR')

views.py

import uuid

from django.shortcuts import render
from rest_framework.decorators import action
from rest_framework.response import Response
from app01 import auth
from app01 import models
from app01 import serializer
# Create your views here.
from rest_framework.viewsets import ModelViewSet, ViewSet


class BookView(ModelViewSet):
    # 需要登录才能访问
    authentication_classes = [auth.LoginAuth, ]
    # 每分钟访问最多五次
    throttle_classes = [auth.TimeThrottle, ]

    serializer_class = serializer.BookSerializer
    queryset = models.Book.objects.all()


class PublishView(ModelViewSet):
    # 需要登录才能访问
    authentication_classes = [auth.LoginAuth, ]
    # 管理员才能访问
    permission_classes = [auth.UserTypePermissions, ]
    # 每分钟访问最多五次
    throttle_classes = [auth.TimeThrottle, ]

    serializer_class = serializer.PublishSerializer
    queryset = models.Publish.objects.all()


class UserView(ViewSet):
    @action(methods=['POST'], detail=False, url_path='login')
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = models.User.objects.filter(username=username, password=password).first()
        if user:
            # 生成随机字符串作为token
            token = uuid.uuid4()
            # 存入UserToken表中 如果没有新增 如果有则修改
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            return Response({'code': 100, 'msg': '登陆成功', 'token': token})
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误'})