DRF 认证、权限、频率

DRF 认证、权限、频率

1. 认证类使用

1.1 使用步骤

# 导入:
	from rest_framework.authentication import BaseAuthentication
	from rest_framework.exceptions import AuthenticationFailed

# 1.写一个认证类继承BaseAuthentication
	class LoginAuth(BaseAuthentication):
        pass
# 2. 重写父类中的authenticate方法
	class LoginAuth(BaseAuthentication):
        def authenticate(self, request):
            pass
# 3. 在内部判断用户是否登录
	--认证条件的数据在哪里?自己规定,可以在请求体,或请求头,路由后。
		--http请求头的数据从request.META中取,统一取:HTTP_请求头大写
        	token = request.META.get('HTTP_TOKEN')
		--请求体在 request.data
        	token =request.data.get('token')
         --路由后在 request.GET中取:
   			token = request.GET.get('HTTP_TOKEN')
	--通过token判断用户是否登录:表中查询。
    	user_token_obj = UserToken.objects.filter(token=token).first()
	--如果登录返回俩个值,一个是当前登录用户,一个是token
    	if user_token_obj:
    		return user_token_obj.user, token
    --如果没登录,直接抛AuthenticationFailed
    	else:
    		raise AuthenticationFailed('你当前暂未登录')
# 4. 认证类写好后,使用
	--局部使用在需要被认证的类中添加
    class BookView(ModelViewSet):
    	authentication_classes = [认证类名,]
        # 局部使用仅在当前需要认证的类中生效
	--全局使用在settings.py中配置
    	REST_FRAMEWORK={
            'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.认证类名称',]
        }
        # 全局使用在所写的所有类中都生效,个别不需要认证需要加入局部禁用
	--局部禁用在需要禁用的类中添加
    	 class BookView(ModelViewSet):
    		authentication_classes = []

1.2 认证类代码

# 新建一个py文件,auth.py 写认证类 LoginAuth
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken

# 写一个认证类并且继承BaseAuthentication
class LoginAuth(BaseAuthentication):
    # 重写父类BaseAuthentication中的authenticate方法
    def authenticate(self, request):
        # 取出token进行校验判断是否登录
        token = request.META.get('HTTP_TOKEN')
        user_token_obj = UserToken.objects.filter(token=token).first()
        if user_token_obj:
            # 如果登录返回俩个值,一个是当前登录用户,一个是token
            return user_token_obj.user, token
        else:
            # 如果没登录,直接抛AuthenticationFailed
            raise AuthenticationFailed('你当前暂未登录')

# 在view.py中使用
	--局部使用
    	class BookView(ModelViewSet):
            
            # 局部使用:authentication_classes = [LoginAuth,]
            
    		authentication_classes = [LoginAuth,]
    		queryset = Book.objects.all()
    		serializer_class = BookSerializer

	--全局使用
    	settings.py中配置
    	REST_FRAMEWORK={
            'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.认证类名称',]
        }

	--局部禁用
    	在全局配置使用中可能有的不需要认证,需要使用局部禁用
        	class BookView(ModelViewSet):
                authentication_classes = []

1.3 认证源码分析

# 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类中找:220行
       def user(self):
            if not hasattr(self, '_user'): # Request类的对象中反射_user
                with wrap_attributeerrors():
                    self._authenticate()  # 第一次会走这个代码
            return self._user
    -Request的self._authenticate()---》373行
        def _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)异常


2.权限类使用

2.1 使用步骤

# 导入
	from rest_framework.permissions import BasePermission

# 1. 写一个权限类继承BasePermission
	class UserTypePermission(BasePermission):
        pass
# 2. 重写父类BasePermission中的has_permission方法
	class UserTypePermission(BasePermission):
    	def has_permission(self, request, view):
# 3. 在方法内部校验用户是否有权限,如果有权限返回True
	 if request.user.user_type == 1:
		return True
# 4. 没有则返回False,可以设置self.message 是给前端的提示信息
	else:
        self.message = "你是2B,没有权限访问"
        return False

# 5. 在view.py中使用
	--局部使用
    	class PublishView(ModelViewSet):
            
    		permission_classes = [UserTypePermission, ]  # 权限校验
            
   			queryset = Publish.objects.all()
    		serializer_class = PublishSerializer

	--全局使用
    	# 在settings.py中添加配置
        	REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ['app01.permission.UserTypePermission',]
}

        	
	--局部禁用
    	在全局配置使用中可能有的不需要使用权限校验,需要使用局部禁用
    	class PublishView(ModelViewSet):
    		permission_classes = []
   			queryset = Publish.objects.all()
    		serializer_class = PublishSerializer

2.2 权限类代码

# 新建一个py文件,permission.py 写权限类 UserTypePermission
permission.py
	# 写一个权限类继承BasePermission
	class UserTypePermission(BasePermission):
        # 也可以直接在这里设置前端提示 message = "你是2B,没有权限访问"
        # 重写has_permission方法
    	def has_permission(self, request, view):
            # 在方法内部校验用户是否有权限
       		if request.user.user_type == 1:
                # 如果有权限,返回True
            	return True
        else:
            # 可以设置self.message 是给前端的提示信息
            self.message = "你是2B,没有权限访问"
            # 没有则返回False
            return False

view.py        
# 局部使用
	class PublishView(ModelViewSet):
		permission_classes = [UserTypePermission, ]  # 权限校验
		queryset = Publish.objects.all()
		serializer_class = PublishSerializer

# 全局使用,在settings.py中添加配置
	REST_FRAMEWORK = {
    	'DEFAULT_PERMISSION_CLASSES':['app01.permission.UserTypePermission',]}

# 局部禁用,在全局配置使用中可能有的不需要使用权限校验,需要使用局部禁用
	class PublishView(ModelViewSet):
		permission_classes = []
		queryset = Publish.objects.all()
		serializer_class = PublishSerializer

2.3 权限类源码分析

-先读最简单的权限执行流程---》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 True或False,messgage可以做什么用

3.频率类使用

3.1 使用步骤

# 导入
	from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# 1. 写一个类继承SimpleRateThrottle
	class Throttle(SimpleRateThrottle):
        pass
# 2. 写一个类属性,scope="xxx"
	class Throttle(SimpleRateThrottle):
        scope = 'xxx'  # xxx可以是任意名字。
    	pass
# 3. 重写SimpleRateThrottle中的def get_cache_key方法
	class Throttle(SimpleRateThrottle):
    	scope = 'xxx'
    def get_cache_key(self, request, view):
        # 返回唯一的字符串,会以这个字符串做频率限制
        return request.META.get('REMOTE_ADDR')
# 4. 配置文件中写
	REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'xxx': '5/m'}}
    xxx:是频率类中scope中的那个字母
    5/m: 表示每分钟可以访问5次,5为次数,m代表时间
        s:每秒、h:每小时、d:每天
# 5. 使用
	--局部配置
    	class PublishView(ModelViewSet):
    		throttle_classes = [Throttle,]
    		queryset = Publish.objects.all()
    		serializer_class = PublishSerializer

    --全局配置
    REST_FRAMEWORK = {
    	'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.Throttle'],
        'DEFAULT_THROTTLE_RATES': {
        'xxx': '5/m'}}
    --局部禁用
    	class PublishView(ModelViewSet):
    		throttle_classes = []
    		queryset = Publish.objects.all()
    		serializer_class = PublishSerializer
    

3.2 频率类代码

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# 写一个类继承SimpleRateThrottle
class Throttle(SimpleRateThrottle):
    # 写一个类属性,scope="xxx"
    scope = 'xxx'
    # 重写SimpleRateThrottle中的def get_cache_key方法
    def get_cache_key(self, request, view):
        # 以访问者的ip地址作为识别条件
        return request.META.get('REMOTE_ADDR')

# 配置文件中
	REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.Throttle'],
    'DEFAULT_THROTTLE_RATES': {
        'xxx': '5/m'
    }
}

3.3 频率类的源码简单分析

# 源码分析:
	-之前咱们读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(到了频率的限制)

4. 鸭子类型

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


--假设有个鸭子类Duck类,有两个方法,run,speak方法
--假设又有一个普通鸭子类,PDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,普通鸭子类的对象就是鸭子这种类型;如果不继承,普通鸭子类的对象就不是鸭子这种类型
# python不推崇这个,它推崇鸭子类型,指的是
不需要显示的继承某个类,只要我的类中有run和speak方法,我就是鸭子这个类

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

练习

继承BaseThrottle编写频率类
class OurThrottle(BaseThrottle):
    aaa = {}
    def allow_request(self, request, view):
          #(1)取出访问者ip
        user_ip = request.META.get('REMOTE_ADDR')
         #(2)判断当前ip不在访问字典里,
        if user_ip not in self.aaa:
            bbb = time.time()  # 获取当前时间
            self.aaa[user_ip] = []  # 将访问者添加到字典里
            self.aaa.get(user_ip).append(bbb)  # 将当前访问者的访问时间添加进去
            # 直接返回True,表示第一次访问
            return True
        else:  # 在字典里
            # 循环判断当前ip的列表,有值
            for i in self.aaa.get(user_ip):
                # 获取当前时间
                ccc = time.time()
                # 当前时间减去列表的中时间
                ddd = ccc - i
                # 判断列表中的时间是否大于
                if ddd > 60:  # 控制的时间
                    index = self.aaa.get(user_ip).index(i)
                    self.aaa.get(user_ip).pop(index)
        eee = time.time()  # 再次时获取当前时间
        # 判断,当列表小于3,说明一分钟以内访问不足三次,
        if len(self.aaa.get(user_ip)) < 3:
            # 把当前时间插入到列表第一个位置,返回True,顺利通过
            self.aaa.get(user_ip).append(eee)
            return True
        else:
            # 当大于等于3,说明一分钟内访问超过三次,返回False验证失败
            return False
posted @ 2022-10-09 21:28  瓮小辉  阅读(46)  评论(0)    收藏  举报