drf07 三大认证之 认证、权限、频率

一、认证

介绍:

认证APIView提供的三大认证之一,使用它视图类必须继承APIView

作用:校验用户是否登录

注意:认证一般搭配权限使用

两个参数:

SessionAuthentication 通过session认证

BasicAuthentication 认证基类

1.内置认证Authentication

1.1 前提准备

# 新建一个app
python manage.py startapp app_auth

# 使用django内置admin站点创建一个管理员 - shell窗口
python manage.py createsuperuser

# 1.用户名
admin
# 2.邮箱 - 可回车跳过
admin@qq.com
# 3.密码
admin123
# 4.确认密码
admin123

# 在settings中配置修改如下内容
LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/shanghai"
USE_TZ = False

# 最后可以登录
http://127.0.0.1:8000/admin/
# 填写用户名密码

1.2 全局配置 -settings

可以在项目的配置文件中全局默认的认证方案:

(1) 查找默认配置

# 1.首先 三大认证是drf中实现的,所以先找到这个
# APIView是drf所有视图的基类
from rest_framework.views import APIView

# 2.点击 APIView 找到这个
  authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    
# 3.点击api_settings
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

# 4.点击DEFAULTS - 这个是rest_framwork的默认配置
DEFAULTS = {
    # Authentication 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],}

(2) rest_framework 默认配置

DEFAULTS = {
    # Authentication 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
    # Throttling 频率
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
    },

    # Pagination 分页
    'PAGE_SIZE': None,

    # Filtering 过滤
    'SEARCH_PARAM': 'search',
    'ORDERING_PARAM': 'ordering',

    # Exception handling 异常处理
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'NON_FIELD_ERRORS_KEY': 'non_field_errors',
}

(3) 在settings配置

# REST_FRAMEWORK,里面都是drf的配置信息
from rest_framework import settings
REST_FRAMEWORK = {
	# 配置认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],

}

(4) 说明

因为默认使用Authentication默认配置,并没有修改默认系统,所以可能会显示你当前登录的用户,但是不会限制你登录后才能方访问,也就是说,游客也可以方法。

重要提示: 认证 需要和权限搭配使用才能产生效果

1.3 局部配置 - 视图类

# views.py 

from rest_framework.viewsets import ModelViewSet
from app05_api import models
from app05_api.ser import BookModelSerializer
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import SessionAuthentication,BasicAuthentication

class BookModelViewSet(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes =[SessionAuthentication,BasicAuthentication] # 局部配置
     permission_classes = [IsAuthenticated] # 判断是否登录,未登录则不给访问

2.自定义认证

2.1 源码分析

#1 APIVIew----》dispatch方法---》self.initial(request, *args, **kwargs)---->有认证,权限,频率
#2 只读认证源码: self.perform_authentication(request)
#3 self.perform_authentication(request)就一句话:request.user,需要去drf的Request对象中找user属性(方法) 
#4 Request类中的user方法,刚开始来,没有_user,走 self._authenticate()

#5 核心,就是Request类的 _authenticate(self):
    def _authenticate(self):
        # 遍历拿到一个个认证器,进行认证
        # self.authenticators配置的一堆认证类产生的认证类对象组成的 list
        #self.authenticators 你在视图类中配置的一个个的认证类:authentication_classes=[认证类1,认证类2],对象的列表
        for authenticator in self.authenticators:
            try:
                # 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象)
                # 返回值:登陆的用户与认证的信息组成的 tuple
                # 该方法被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()

2.2 如何查找认证类

# 1. 找到这个 点击 BasicAuthentication
from rest_framework.authentication import SessionAuthentication,BasicAuthentication

# 2. 点击 BaseAuthentication
class BasicAuthentication(BaseAuthentication)

# 3. 找到这里,这里可以看到,基本的认证是要重写 authenticate
# 这里说明以下,这里规定了认证需要重写的方法,参照鸭子类型,可以不继承这个自己写一个

class BaseAuthentication:
    def authenticate(self, request):
        raise NotImplementedError(".authenticate() must be overridden.")

2.3 编写自定义认证类

前提准备 - 自定义类认证前提

# models.py 
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)  #一对一关联到User表

# 别忘记在User表中添加数据

# views.py - 获取登录信息 - 并传如token 随机字符串
import uuid
from rest_framework.views import APIView
from app06_auth import models

class LoginView(APIView):
    authentication_classes = []
    def post(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=uuid.uuid4()
            # 存到UserToken表中
            # models.UserToken.objects.create(token=token,user=user)# 用它每次登陆都会记录一条,不好,如有有记录
            # update_or_create有就更新,没有就新增
            models.UserToken.objects.update_or_create(defaults={'token':token},user=user)
            return Response({'status':100,'msg':'登陆成功','token':token})
        else:
            return Response({'status': 101, 'msg': '用户名或密码错误'})

        
        
# urls.py
from django.urls import path, re_path
from app06_auth import views
from rest_framework import routers

router = routers.SimpleRouter()

router.register('books', views.BookModelViewSet) # 不用加后缀

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

注意事项

1.需要先登录,才能获得token

2.访问必须要带token

成功后拿到数据

登录失败后,访问不了

认证逻辑

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app06_auth import models
class MyAuthentication(BaseAuthentication):

    def authenticate(self,request):
        # 认证逻辑,如果认证通过,返回以元组的形式返回两个值  (user, token)
        token = request.GET.get('token')
        if token:
            user_token = models.UserToken.objects.filter(token=token).first()
            # 认证通过
            if user_token:
                # print(user_token.user) # 解释   user_token.user 值得是user对象
                return user_token.user,token
            else:
                raise AuthenticationFailed('认证失败')
        else:
            raise AuthenticationFailed('请求地址中需要携带token')

2.4 自定义认证类配置

2.4.1 全局配置 -settings

在项目的setting中配置如下

# 在项目的setting中配置如下

REST_FRAMEWORK = {

  'DEFAULT_AUTHENTICATION_CLASSES': [ 
        # 自定义认证类 - 类的路径 
        # from app06_auth.utils.app_auth_utils import MyAuthentication
        'app06_auth.utils.app_auth_utils.MyAuthentication',
    ],

}

2.4.2 局部配置 - 视图类

# views.py

from rest_framework.viewsets import ModelViewSet
from app05_api import models
from app05_api.ser import BookModelSerializer
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from app06_auth.utils.app_auth_utils import MyAuthentication
class BookModelViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # authentication_classes =[SessionAuthentication,BasicAuthentication] # 局部配置
    authentication_classes = [MyAuthentication]

二、权限

介绍:

权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。

  • 在执行视图的dispatch()方法前,会先进行视图访问权限的判断
  • 在通过get_object()获取具体对象时,会进行模型对象访问权限的判断

两个参数:

IsAuthenticated 是否登录

IsAdminUser 是否为管理员

1. 内置权限

用法在内置认证中已经写了,这里就不在补充。这里写下如何配置。并且权限之前需要有认证才行。

1.1 全局配置 -settings

在项目的settings中配置如下:

# 在项目的settings中配置如下:
# 登录用户 - 才能访问
from rest_framework.permissions import IsAuthenticated
# 管理员用户 - 管理员才能访问
from rest_framework.permissions import IsAdminUser


# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 自定义认证类
        # 'app06_auth.utils.app_auth_utils.MyAuthentication',
        # 默认认证类
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ],

    'DEFAULT_PERMISSION_CLASSES': [
        # 默认权限类
        'rest_framework.permissions.AllowAny',
        # 是否登录
        'rest_framework.permissions.IsAuthenticated',
        # 是否 为管理员
        'rest_framework.permissions.IsAdminUser',
    ],
}

1.2 局部配置 - 视图类

# views.py 
# 可单个使用 最好搭配认证


from rest_framework.viewsets import ModelViewSet
from app05_api.models import Book
from app05_api.ser import BookModelSerializer
from rest_framework.permissions import IsAuthenticated,IsAdminUser
from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from app06_auth.utils.app_auth_utils import MyAuthentication
# Create your views here.
class BookModelViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # authentication_classes =[SessionAuthentication,BasicAuthentication] # 局部配置
    # authentication_classes = [MyAuthentication]
    permission_classes = [IsAuthenticated,IsAdminUser]

2. 自定义权限

2.1 源码分析

# 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)
                )

2.2 如何查找权限类

# 1. 找到这个 点击 IsAuthenticated
from rest_framework.permissions import IsAuthenticated,IsAdminUser

# 2. 看到这个 点击 BasePermission
class IsAuthenticated(BasePermission):

# 3. 看到如下内容
class BasePermission(metaclass=BasePermissionMetaclass):
    
    def has_permission(self, request, view):
        return True
    
    def has_object_permission(self, request, view, obj):
        return True
    
# 注意:里面还继承了元类,这里就不再深入

2.3 编写自定义认证类

前提准备

准备同 认证类 -> 2.3 -> 前期准备

注意事项

1.需要先登录,才能获得token

2.访问必须要带token

权限逻辑

from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
    
    def has_permission(self, request, view):

        user= request.user # 当前用户的user 是认证传过来的
        # 由于经过认证后,request内就有user对象了,这个时候就能拿到登录用户的用户类型
        # 如果该字段用了choice,别忘了用get_字段名_display()就能取出choice后面的中文
        try:
            if user.user_type == 1:
                return True # 表示通过权限
            else:
                return False # 表示未通过权限,会报相应的错误信息
        except:
            return False

2.4 自定义权限类配置

前提准备 - 参考认证

# views.py 
class LoginView(APIView):
    authentication_classes = []
    permission_classes = []
    def post(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=uuid.uuid4()
            # 存到UserToken表中
            # models.UserToken.objects.create(token=token,user=user)# 用它每次登陆都会记录一条,不好,如有有记录
            # update_or_create有就更新,没有就新增
            models.UserToken.objects.update_or_create(defaults={'token':token},user=user)
            return Response({'status':100,'msg':'登陆成功','token':token})
        else:
            return Response({'status': 101, 'msg': '用户名或密码错误'})
        
        
        
# urls.py
from django.urls import path, re_path
from app06_auth import views
from rest_framework import routers

router = routers.SimpleRouter()

# router.register('books/', views.BookModelViewSet)
router.register('books', views.BookModelViewSet) # 不用加后缀

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

2.4.1 全局配置 - settings

from rest_framework import settings
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 自定义认证类
        'app06_auth.utils.app_auth.MyAuthentication',
        # 默认认证类
        # 'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.BasicAuthentication',
    ],

    'DEFAULT_PERMISSION_CLASSES': [
        # 自定义权限类
        'app06_auth.utils.app_permission.MyPermission'
        # 默认权限类
        # 'rest_framework.permissions.AllowAny',
        # 是否登录
        # 'rest_framework.permissions.IsAuthenticated',
        # 是否 为管理员
        # 'rest_framework.permissions.IsAdminUser',
    ],
}

2.4.2 局部配置 - 视图类

from rest_framework.viewsets import ModelViewSet
from app05_api.models import Book
from app05_api.ser import BookModelSerializer
from rest_framework.permissions import IsAuthenticated,IsAdminUser
from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from app06_auth.utils.app_auth import MyAuthentication
from app06_auth.utils.app_permission import MyPermission

class BookModelViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # authentication_classes =[SessionAuthentication,BasicAuthentication] # 局部配置
    authentication_classes = [MyAuthentication]
    # permission_classes = [IsAuthenticated,IsAdminUser] # 局部配置
    permission_classes = [MyPermission]

三、限流(频率)Throttling

介绍:

​ 用来限制用户的访问频率

两个参数

​ AnonRateThrottle 游客 - 未登录

​ UserRateThrottle 用户 - 登录

1. 内置限流

1.1 全局使用 - settings

这里添加了默认认证类 和权限 不在settings中配置就会用默认的

REST_FRAMEWORK = {
    # Authentication
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 默认认证类
        'rest_framework.authentication.SessionAuthentication',
    ],
    # Permission
    'DEFAULT_PERMISSION_CLASSES': [
        # 是否登录
        # 'rest_framework.permissions.IsAuthenticated',
    ],
    # Throttling
    # 配置角色
    'DEFAULT_THROTTLE_CLASSES': [
        # 未登录 - 游客
        'rest_framework.throttling.AnonRateThrottle',
        # 登录 - 用户
        'rest_framework.throttling.UserRateThrottle',
    ],
    # 设置频率
    'DEFAULT_THROTTLE_RATES': {
        'user': '10/m',
        'anon': '3/m',
    },

}

1.2 局部使用 - 视图类

注意:无论全局还是局部,都要在settings中设置频率

from rest_framework.viewsets import ModelViewSet
from app05_api.models import Book
from app05_api.ser import BookModelSerializer
from rest_framework.throttling import AnonRateThrottle,UserRateThrottle
# Create your views here.
class BookModelViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # 认证
    # authentication_classes =[SessionAuthentication,BasicAuthentication] # 局部配置
    # authentication_classes = [MyAuthentication]
    # 权限
    # permission_classes = [IsAuthenticated,IsAdminUser]
    # permission_classes = [MyPermission]
    # 限流  
    # AnonRateThrottle 未登录
    # UserRateThrottle 登录
    throttle_classes = [AnonRateThrottle,UserRateThrottle]

2.自定义限流(频率)

2.1源码分析

# SimpleRateThrottle源码分析
    def get_rate(self):
        """
        Determine the string representation of the allowed request rate.
        """
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            return self.THROTTLE_RATES[self.scope]  # scope:'user' => '3/min'
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
    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)
        #3  mmmmm
        num, period = rate.split('/')  # rate:'3/min'
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)
    def allow_request(self, request, view):
        if self.rate is None:
            return True
        #当前登录用户的ip地址
        self.key = self.get_cache_key(request, view)  # key:'throttle_user_1'
        if self.key is None:
            return True

        # 初次访问缓存为空,self.history为[],是存放时间的列表
        self.history = self.cache.get(self.key, [])
        # 获取一下当前时间,存放到 self.now
        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration

        # 当前访问与第一次访问时间间隔如果大于60s,第一次记录清除,不再算作一次计数
        # 10 20 30 40
        # self.history:[10:23,10:55]
        # now:10:56
        while self.history and  self.now - self.history[-1] >= self.duration:
            self.history.pop()

        # history的长度与限制次数3进行比较
        # history 长度第一次访问0,第二次访问1,第三次访问2,第四次访问3失败
        if len(self.history) >= self.num_requests:
            # 直接返回False,代表频率限制了
            return self.throttle_failure()

        # history的长度未达到限制次数3,代表可以访问
        # 将当前时间插入到history列表的开头,将history列表作为数据存到缓存中,key是throttle_user_1,过期时间60s
        return self.throttle_success()

2.2 如何查找限流类

# 1.找到这个 然后点 SimpleRateThrottle - 其实这个已经够用了
from rest_framework.throttling import SimpleRateThrottle

# 2.看到这个 然后点 BaseThrottle
class SimpleRateThrottle(BaseThrottle):
    
# 3.重写 allow_request 方法
class BaseThrottle:
 
    def allow_request(self, request, view):
        raise NotImplementedError('.allow_request() must be overridden')

    def wait(self):
        return None

2.3 编写自定义限流

(1)根据IP限流 - 难度版

import time
# 其实这个没必要自己去写,逻辑还没有别人写的好呢
class MyThrottle():
    scope = 'test' # 设置这个后,需要与频率对应好
    #定义成类属性,所有对象用的都是这个
    VISIT_DIC = {}
    def __init__(self):
        self.history_list=[]
    def allow_request(self, request, view):
        '''
        #(1)取出访问者ip
        #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
        #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        '''

        ip=request.META.get('REMOTE_ADDR')
        ctime=time.time()
        if ip not in self.VISIT_DIC:
            self.VISIT_DIC[ip]=[ctime,]
            return True
        self.history_list=self.VISIT_DIC[ip]   #当前访问者时间列表拿出来
        while True:
            if ctime-self.history_list[-1]>60:
                self.history_list.pop() # 把最后一个移除
            else:
                break
        if len(self.history_list)<3:
            self.history_list.insert(0,ctime)
            return True
        else:
            return False

    def wait(self):
        # 当前时间,减去列表中最后一个时间
        ctime=time.time()

        return 60-(ctime-self.history_list[-1])

(2) 任意指标限流 - 简单版

from rest_framework.throttling import SimpleRateThrottle

class MyThrottle(SimpleRateThrottle):
    scope = 'sms'
    def get_cache_key(self, request, view):
        telephone = request.query_params.get('telephone')
        #'throttle_%(scope)s_%(ident)s'%{}
        return self.cache_format%{'scope':self.scope,'ident':telephone}

2.4 自定义限流类配置

2.4.1 全局配置 - settings

REST_FRAMEWORK = {
    # Throttling
    # 配置角色
    'DEFAULT_THROTTLE_CLASSES': [
        # 自定义角色 - ip 限流
        'app06_auth.utils.app_throttling.MyThrottle',
        # 默认角色
        # 未登录 - 游客
        # 'rest_framework.throttling.AnonRateThrottle',
        # 登录 - 用户
        # 'rest_framework.throttling.UserRateThrottle',
    ],
    # 设置频率
    'DEFAULT_THROTTLE_RATES': {
        # 'user': '10/m',
        # 'anon': '3/m',
        'test':'3/m', # 自定义限流类 scope='test'
    },

}

2.4.2 局部配置 - 视图类

from rest_framework.viewsets import ModelViewSet
from app05_api.models import Book
from app05_api.ser import BookModelSerializer
from rest_framework.permissions import IsAuthenticated,IsAdminUser
from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from app06_auth.utils.app_auth import MyAuthentication
from app06_auth.utils.app_permission import MyPermission
from app06_auth.utils.app_throttling import MyThrottle

class BookModelViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # authentication_classes =[SessionAuthentication,BasicAuthentication] # 局部配置
    authentication_classes = [MyAuthentication]
    # permission_classes = [IsAuthenticated,IsAdminUser] # 局部配置
    permission_classes = [MyPermission]
    # 限流
    # throttle_classes = [AnonRateThrottle,UserRateThrottle]
    throttle_classes = [MyThrottle]
    
posted @ 2023-05-30 21:04  派森的猫  阅读(70)  评论(0)    收藏  举报