drf认证、权限、频率


目录
  • 认证Authentication(5星)
    • 认证逻辑及编写步骤
    • 使用方法
      • 案例
    • 全局使用
    • 局部使用
    • 源码分析(2星)
  • 权限Permissions(4星)
    • 使用方法
    • 全局使用
    • 局部使用
    • 源码分析(2星)
  • 频率Throttling
    • 继承SimpleRateThrottle使用方法
    • 全局使用
    • 局部使用
    • 源码分析SimpleRateThrottle(2星)
    • 源码分析BaseThrottle(2星)
    • 继承BaseThrottle使用方法

认证Authentication(5星)

认证逻辑及编写步骤

逻辑

认证类:用来校验用户是否登录,如果登录了,继续往下走,如果没有登录,直接返回

编写步骤

   -第一步:写一个类,继承BaseAuthentication,重写authenticate,在方法中做校验,校验是否登录,返回两个值,没有登录抛异常
  -第二步:全局配置,局部配置
  	-全局配置:配置文件中
      REST_FRAMEWORK={
      "DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth",]
  		}
    -局部配置:在视图类中
      class UserView(ViewSet):
        authentication_classes = [LoginAuth] 
        
    -局部禁用:
       class UserView(ViewSet):
        	authentication_classes = [] 

# 认证类中返回的两个变量,干啥用了
  -返回的第一个,给了request.user,就是当前登录用户
  -返回的第二个,给了request.auth,就是token串

使用方法

新建一个功能为认证的py文件,然后在里面书写下面代码

from rest_framework.authentication import BaseAuthentication   # 类必须继承这个
from rest_framework.exceptions import AuthenticationFailed  # 认证报错的异常

class xxx(BaseAuthentication):
    def authenticate(self, request):  # 必须重写这个方法,不然报错
        逻辑逻辑逻辑...
        if 验证通过:
            return
        else:
            raise AuthenticationFailed('验证不通过')

在需要验证的视图类里

class BookAPIView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # 只需要把下面这句话加上就行了 那么在执行该视图类前会判断该类是否通过验证
    # 通过则正常执行,不通过则返回报错信息
    authentication_classes = [xxx, ]  

案例

登陆认证

在路由里:

from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('book', views.BookAPIView)

urlpatterns = [
    path('login/', views.UserView.as_view()),
    path('', include(router.urls))
]

在models.py里:

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)

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

在认证的py文件里:

# 新建一个py文件auth.py
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        #### 写认证规则
        # 取出用户携带的token
        token = request.query_params.get('token')
        # 去数据库查找
        user_token = UserToken.objects.filter(token=token)
        if user_token:
            # 说明登陆过
            '''
            如果返回user,token,后面视图类中通过request对象,可以取到当前登录用户
            '''
            return
        else:
            # 没有登录,不能继续往后走,抛认证失败异常
            raise AuthenticationFailed('您未登陆')

在视图函数里:

from app01.auth import LoginAuth

class BookAPIView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth, ]  # 这里验证是否登陆

因为数据库内已经有一个token,所以拿这个token验证

全局使用

在django的settings.py中设置

这里不要手打,容易出错,用模块导入的形式来写

from app01.auth import LoginAuth

然后把import删除加个点

app01.auth.LoginAuth

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth",]
}

局部使用

# 局部使用,只需要在视图类里加入:
authentication_classes = [LoginAuth, ]
# 局部禁用  直接写个空列表就行了
authentication_classes = []

认证类的查找顺序

视图类自己中的authentication_classes > 项目的配置文件 > drf的配置文件

源码分析(2星)

# APIView--->dispathch方法的-----》self.initial(request, *args, **kwargs)(497行)---》APIView的initial方法----》有三句话
self.perform_authentication(request)  # 认证
self.check_permissions(request)       # 权限
self.check_throttles(request)         # 频率

image

image

image

image

image

image

image

image

image

image

image

补充知识点:

image

权限Permissions(4星)

使用方法

在一个py文件内:

from rest_framework.permissions import BasePermission  # 继承这个类
class xxx(BasePermission):
    def has_permission(self, request, view):  # 重写此方法
        # 写权限逻辑
        if 符合权限:
            return True  # 有权限
        else:
            return False  # 无权限

在需要验证的视图类里:

# 把权限的py文件导入
class BookAPIView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # 只需要把下面这句话加上就行了 那么在执行该视图类前会判断该类是否有权限
    # 没权限则不会执行该视图类
    permission_classes = [xxx, ]

全局使用

REST_FRAMEWORK={
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]
}

局部使用

# 局部使用只需要在视图类里加入:
permission_classes = [UserPermission,]

# 局部禁用
permission_classes = []  # 什么都不写就是禁用

源码分析(2星)

和认证的源码差不多,只是没有和Request联系

APIView---》dispatch-self.initial---》self.check_permissions(request)
   for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )

image

频率Throttling

可以对接口访问的频次进行限制,以减轻服务器压力。

一般用于付费购买次数,投票等场景使用.

继承SimpleRateThrottle使用方法

这里继承SimpleRateThrottle

在一个py文件内:

from rest_framework.throttling import SimpleRateThrottle  # 必须继承这个类

class xxx(SimpleRateThrottle):
    scope = 'ip_throtle'  # 这个参数必须配置,不然会报错,后面的字符串随便写
    # 或者
    rate = '3/m' # 这样写下面的配置文件里的'ip_throtle': '3/m'就可以不用写了
    def get_cache_key(self, request, view):
        # 返回客户端IP  返回谁,就以谁做限制  按ip限制
        return request.META.get('REMOTE_ADDR')
    	# 或
        return self.get_ident(request)  # 如果有代理也会返回真实的客户端IP

在配置文件settings.py内:

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'ip_throtle': '3/m'  # key要跟类中的scope对应  '3/m'意思是每分钟最多访问3次
    }
}

 # 使用 `second`, `minute`, `hour` 或`day`来指明周期。

在需要验证限流的视图类里:

from app01.auth import xxx

class BookAPIView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    throttle_classes = [xxx, ]  # 在这里添加验证限流

全局使用

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['app01.auth.MyThrottle'],  # 全局使用
    'DEFAULT_THROTTLE_RATES': {
        'ip_throtle': '3/m'  # key要跟类中的scope对应  '3/m'意思是每分钟最多访问3次
    }
}

局部使用

class BookView(ViewSetMixin,ListAPIView):
    throttle_classes = [MyThrottle,]

源码分析SimpleRateThrottle(2星)

image

image

image

源码分析BaseThrottle(2星)

APIVIew的dispatch---》self.initial(request, *args, **kwargs)----》
    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())

        if throttle_durations:
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)

image

image

继承BaseThrottle使用方法

# 自定义的逻辑
#(1)取出访问者ip
#(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
#(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
#(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
#(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
# {'ip1':[时间1,时间2,时间3], 'ip2':[时间1,时间2]}
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])

image