drf-认证、权限、频率组件
目录
-认证组件
--认证简介
只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录,就不能查看,这时候需要用到认证组件
--使用方法
在app下新建一个.py文件,在其中写一个认证类(必须继承BaseAuthentication),在该类中重写authenticate方法,认证逻辑都写在其中。若认证通过,返回两个值;认证失败,抛出异常APIException或AuthenticationFailed(继承的是APIException,一般都用它)。
--源码分析
APIView-->dispatch()方法-->request = self.initialize_request(request, *args, **kwargs)--> initialize_request()返回Request()对象,里面的authenticators从get_authenticators()中得到--> get_authenticators(),返回return [auth() for auth in self.authentication_classes],列表生成 式,从配置的authentication_classes中一个个取类加括号生成对象-->authentication_classes在APIView 类最初配置的authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES-->搞清楚 authenticators从哪来的后-->再看dispatch方法中的关键-->self.initial(request, *args, **kwargs) -->initial()中有三大组件——认证、权限、频率: # 三大组件-认证 校验用户:游客、合法用户、非法用户 # 游客:代表校验通过,直接进入下一步校验(权限校验) # 合法用户:代表校验通过,将用户存在request.user中,再进入下一步校验(权限校验) # 非法用户:代表校验失败,抛出异常,返回403forbidden self.perform_authentication(request) # 三大组件-权限 校验用户权限:必须登录、所有用户、登录读写游客只读、自定义用户角色 # 认证通过:可以进入下一步校验 # 认证失败:抛出异常403 self.check_permissions(request) # 三大组件-频率 限制视图接口被访问的频率数:限制条件(ip,id)、频率周期时间(s,m,h)、频率次数(几次/s) # 没有达到限次:正常访问接口 # 达到限次:限制时间内不能访问,限制时间后可以访问 self.check_throttles(request) -->先看认证perform_authentication()-->只有一句话request.user-->此user来自drf的Request,应去 drf的Request类中找user属性/方法-->点击Ctrl+user去drf的Request-->self._authenticate()
核心:
def _authenticate(self): for authenticator in self.authenticators: # authenticators是可迭代对象,是一个列表,里面是配置的认证类加括号产生的对象 # 拿到一个个认证器,进行认证 try: # 认证器调用认证方法authenticate(自己的认证类重写的) # 该方法应返回一个包含user,auth的元组 # 该方法被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()
--使用
'''首先新建一个用户表,用于校验登录信息。这自己写了个token,并单独建了一个和User一对一关联的UserToken表''' 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)
'''我在app下新建了user_auth.py文件,里面写认证类''' from rest_framework.authentication import BaseAuthentication from app01 import models from rest_framework.exceptions import AuthenticationFailed class MyAuthentication1(BaseAuthentication): # 重写authenticate方法 def authenticate(self, request): token = request.GET.get('token') # token一般放在浏览器中,这为了演示方便把它放在url?后 if token: user_token_obj = models.UserToken.objects.filter(token=token).first() if user_token_obj: # 认证通过 返回两个值 return user_token_obj.user, token else: # 认证失败 抛异常 raise AuthenticationFailed('认证失败') else: raise AuthenticationFailed('请求中未携带token认证信息!')
# 登录视图类 from rest_framework.views import APIView from rest_framework.response import Response from app01 import models import uuid class LoginAPIView(APIView): # 登录发的是post请求 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: # 登录成功, 生成一个随机字符串 token = uuid.uuid4() # 生成随机token,利用uuid模块,几乎不会重复 # 将token存到UserToken表中 # 此方法每次登录都会存一个token,既耗表资源也不恰当 # models.UserToken.objects.create(token=token, user=user_obj) # 我们需要的是一个用户就一行表资源,每次登录将token更新即可, # 而一次也未登录的应该生成token存在UserToken表中 models.UserToken.objects.update_or_create(defaults={'token':token}, user=user_obj) # update_or_create(self, defaults=None, **kwargs)使用给定的kwargs查找一个对象,如果对象存在,则用默认值更新它,否则创建一个新对象。 return Response({'status':100,'msg':'登录成功','token':token}) else: return Response({'status':101,'msg':'用户名或密码错误'})
认证类使用顺序:先用视图类中的验证类,再用settings里配置的验证类,最后用默认的验证类
---局部使用
局部使用,只需要在视图类里加入:
authentication_classes = [MyAuthentication1, ]
from rest_framework.viewsets import ModelViewSet from app01.user_auth import MyAuthentication1 class PublishModelViewSet(ModelViewSet): authentication_classes = [MyAuthentication1] queryset = models.Publish.objects serializer_class = PublishSerializers ''' 此时get请求访问所有: http: //127.0.0.1:8000/publishes/?token=12a3bb54-fe89-4451-ae1a-b3891659b523 '''
---全局使用
'''所有视图类都认证,去setting.py中配置:''' REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES':['app01.user_auth.MyAuthentication1',] } # 是列表,可多个
---局部禁用
'''对配了全局的又想某些视图类不用,则在该视图类中重赋值为空''' authentication_classes = []
-权限组件
之前的auth模块里自带权限校验is_staff(),这学的是drf的我们自己写的权限校验,但其实drf也有对应方法支持之前的auth模块带的权限校验,后面会有
--使用方法
在.py文件中新建一个类,继承BasePermission,重写has_permissioin方法,如果权限通过返回True;权限不通过返回False。
--源码分析
APIView-->dispatch()方法-->self.initial(request, *args, **kwargs)--> self.check_permissions(request)-->def check_permissions(self, request) -----核心: def check_permissions(self, request): # 遍历权限对象列表得到一个个权限对象(权限器),进行权限认证 for permission in self.get_permissions(): # 权限类一定得有个has_permission方法,用来写权限认证逻辑 # 参数:权限对象self,请求对象request,视图类对象view # 返回值:有权限True,无权限False if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None) )
--使用
from rest_framework.permissions import BasePermission class MyPermission1(BasePermission): # 重写has_permission方法 def has_permission(self, request, view): # 是超级用户才能访问指定内容 user_obj = request.user # 由于已经认证过了,所以request中有user print(user_obj.get_user_type_display()) # 显示choices字段对应的中文信息:对象.get_字段名_display() if user_obj.user_type == 1: return True else: return False
model.py沿用认证里的
path('permit1/', views.PublishAPIViewPermissionTest1.as_view()), path('permit2/', views.PublishAPIViewPermissionTest2.as_view()),
权限类使用顺序:先用视图类中的权限类,再用settings里配置的权限类,最后用默认的权限类
---局部使用
from app01 import user_permission class PublishAPIViewPermissionTest1(APIView): authentication_classes = [MyAuthentication1] permission_classes = [user_permission.MyPermission1] def get(self, request): return Response('权限测试,只有超级用户才能看到哦~') class PublishAPIViewPermissionTest2(APIView): authentication_classes = [MyAuthentication1] def get(self, request): return Response('权限测试,普通用户也能看到哦~')
---全局使用
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES':['app01.user_permission.MyPermission1'], # 权限 } # 注意:在REST_FRAMEWORK里面配置就行了,不能命两个!!!
---局部禁用
--内置权限校验
drf内置的权限校验只能是通过admin中设置的用户,因为它用了IsAdminUser等它内置的
# 内置权限IsAdminUser演示 # 1、创建超级管理员createsuperuser # 2、写一个测试视图类 from rest_framework.permissions import IsAdminUser from rest_framework.authentication import SessionAuthentication class TestView(APIView): authentication_classes = [SessionAuthentication, ] permission_classes = [IsAdminUser] def get(self, request): return Response('superuser可看') # 3、超级用户登录admin,再访问test就有权限 # 4、正常用户没有权限看
-频率组件
频率即控制某个用户访问某个url的次数,如一分钟内只能三次等
drf内置的频率校验只能操作到admin用户或通过admin后台管理设置的用户
--内置频率类校验使用
drf自带的内置频率校验类有匿名用户类(未登录用户)AnonRateThrottle,登录用户类UserRateThrottle。
---局部使用
在视图类下配置throttle_classes = [AnonRateThrottle] # 内置匿名用户限制 在settings.py中的REST_FRAMEWORK中写'DEFAULT_THROTTLE_RATES':{'anon':'3/m'} # 限制频率次数,1min(h-hour,y-year…)内只能3次,anon与AnonRateThrottle对应;user与UserRateThrottle对应
---全局使用
在settings.py中配两个参数 REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSER':['rest_framework.throttling_UserRateThrottle'], 'DEFAULT_THROTTLE_RATES':{'user':'10/m'} # 必带 }
--自定义频率
自定义频率校验实现逻辑如下,就是要重写allow_request()和wait()两个方法,继不继承BaseThrottle都行因为Python实行的是鸭子类型(我有鸭子的两个方法特征了,我就是鸭子了),但实现的较low,3/60写的死不动态,可查看SimpleRateThrottle类的源码学习思维
'''自定义的逻辑''' # (1)取出访问者ip # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间, # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过 # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败 eg: 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])
--源码分析
class SimpleRateThrottle(BaseThrottle): # 咱自己写的放在了全局变量,他的在django的缓存中 cache = default_cache # 获取当前时间,跟咱写的一样 timer = time.time # 做了一个字符串格式化, cache_format = 'throttle_%(scope)s_%(ident)s' scope = None # 从配置文件中取DEFAULT_THROTTLE_RATES,所以咱配置文件中应该配置,否则报错 THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES def __init__(self): if not getattr(self, 'rate', None): # 从配置文件中找出scope配置的名字对应的值,比如咱写的‘3/m’,他取出来 self.rate = self.get_rate() # 解析'3/m',解析成 3 m self.num_requests, self.duration = self.parse_rate(self.rate) # 这个方法需要重写 def get_cache_key(self, request, view): """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ raise NotImplementedError('.get_cache_key() must be overridden') def get_rate(self): if not getattr(self, 'scope', None): msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % self.__class__.__name__) raise ImproperlyConfigured(msg) try: # 获取在setting里配置的字典中的之,self.scope是 咱写的luffy return self.THROTTLE_RATES[self.scope] except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) # 解析 3/m这种传参 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) num, period = rate.split('/') num_requests = int(num) # 只取了第一位,也就是 3/mimmmmmmm也是代表一分钟 duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] return (num_requests, duration) # 逻辑跟咱自定义的相同 def allow_request(self, request, view): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ if self.rate is None: return True self.key = self.get_cache_key(request, view) if self.key is None: return True self.history = self.cache.get(self.key, []) self.now = self.timer() # Drop any requests from the history which have now passed the # throttle duration while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() if len(self.history) >= self.num_requests: return self.throttle_failure() return self.throttle_success() # 成功返回true,并且插入到缓存中 def throttle_success(self): """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True # 失败返回false def throttle_failure(self): """ Called when a request to the API has failed due to throttling. """ return False def wait(self): """ Returns the recommended next request time in seconds. """ if self.history: remaining_duration = self.duration - (self.now - self.history[-1]) else: remaining_duration = self.duration available_requests = self.num_requests - len(self.history) + 1 if available_requests <= 0: return None return remaining_duration / float(available_requests)