10.9内容整理和概述
今日内容概要
内容目录
- request--认证
- request--权限
- request--频率
- 自定义频率类
- 内置认证类,权限类,频率类
- 鸭子类型
- 战略补充
request--认证
应用背景:访问接口时,我们需要对用户的身份进行认证,符合要求的才允许访问 # 比如登录
步骤:
1.新建py文件,用于编写编写认证类
2.导包:from rest_framework.authentication import BaseAuthentication
3.认证类继承BaseAuthentication类
4.需要重写authenticate方法,并传入request参数 # 例子: def authenticate(self, request): pass
5.在authenticate方法写入认证逻辑
6.认证通过返回两个数据(登录用户+标记数据);不通过抛认证类异常AuthenticationFailed # 认证类异常导包:from rest_framework.exceptions import AuthenticationFailed————继承于APIException
认证类的部署:
1.全局配置:
步骤:
1.在app的settings.py文件中修改REST_FRAMEWORK属性,添加DEFAULT_AUTHENTICATION_CLASSES属性
2.DEFAULT_AUTHENTICATION_CLASSES属性对应数据为列表套类名
例子:
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
}
2.局部部署:
步骤:
1.在视图类的py文件中导入认证类
2.在需要认证的视图类中添加authentication_classes属性
3.authentication_classes属性对应数据类型为列表,列表元素为认证类 # 例子:authentication_classes = [LoginAuth,]
3.局部禁用:
应用背景:在全局部署认证的情况下,对单独视图类解放认证
步骤:
1.在需要解放认证的视图类中添加authentication_classes属性
2.令authentication_classes属性等于空列表 # 例子:authentication_classes = []
'--------------认证类源码分析----------------'
执行流程:
1.视图类继承APIView,通过路由匹配调用as_view方法
2.当前类中没有as_view方法,去调用父类APIView的as_view方法,取消了csrf认证,并调用View类的as_view方法
3.View类的as_view方法调用APIView类的dispatch方法
4.dispatch方法中包装了新的request,并执行APIView类的initial方法对request进行认证,频率,权限 # 源码:self.initial(request, *args, **kwargs)
5.perform_authentication方法是APIView类的initial方法中对认证的实现 # 源码:self.perform_authentication(request)
6.perform_authentication方法执行了request.user # 当前request是包装过的新request
7.去Request类中调用user方法
功能介绍:通过反射机制判断执行次数,第一次调用时执行_authenticate方法,之后直接返回self._user
源码:
def user(self):
if not hasattr(self, '_user'): # Request类的对象中反射_user
with wrap_attributeerrors():
self._authenticate() # 第一次会走这个代码
return self._user
8.第一次调用执行Request类的_authenticate方法
执行流程:
1.for循环视图类中的认证类列表 # 源码:for authenticator in self.authenticators
2.每次循环调用对应视图类的写的认证方法--authenticate方法,认证通过用一个元组接收返回结果;认证失败抛异常 # 源码:user_auth_tuple = authenticator.authenticate(self)
3.如果元组有值,解压赋值给新的request的user和auth属性 # 源码:self.user, self.auth = user_auth_tuple
4.解压赋值成功,执行return # 说明在视图类的认证部署中可以部署多个认证类,但是只要成功一个,后续认证类将不再执行认证功能
大体源码:
def _authenticate(self):
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
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
request--认证类例子
from .models import UserToken
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 在这里做认证,校验用户是否登录(带了token,并且能查到,就是登录,返回两个值,否则就是没登录,抛异常)
# 用户带的token从哪取?后端人员定的:放在请求地址中
token = request.GET.get('token')
# 通过token查询该token是否是在表中有记录
user_token = UserToken.objects.filter(token=token).first()
if user_token:
return user_token.user, token # 返回两个值,一个是当前登录用户,一个是token
else:
raise AuthenticationFailed('您没有登录')
request--权限
应用背景:通过认证的用户,也可以通过权限的不同,进行不同API接口的使用
步骤:
1.新建py文件,用于编写编写权限类
2.导包:from rest_framework.permissions import BasePermission
3.权限类继承BasePermission类
4.需要重写has_permission方法,并传入request,view参数 # 例子: def has_permission(self, request, view): pass
5.在has_permission方法写入权限逻辑
6.认证通过返回True;不通过返回False
7.message参数:当权限不通过时,系统会返回message参数的提示信息数据给前端
权限类的部署:
1.全局配置:
步骤:
1.在app的settings.py文件中修改REST_FRAMEWORK属性,添加DEFAULT_PERMISSION_CLASSES属性
2.DEFAULT_PERMISSION_CLASSES属性对应数据为列表套类名
例子:
REST_FRAMEWORK={
'DEFAULT_PERMISSION_CLASSES':['app01.permission.UserTypePermission',]
}
2.局部部署:
步骤:
1.在视图类的py文件中导入权限类
2.在需要权限的视图类中添加permission_classes属性
3.permission_classes属性对应数据类型为列表,列表元素为权限类 # 例子:permission_classes = [UserTypePermission,]
3.局部禁用:
应用背景:在全局部署权限的情况下,对单独视图类解放权限
步骤:
1.在需要解放权限的视图类中添加permission_classes属性
2.令permission_classes属性等于空列表 # 例子:permission_classes = []
'--------------权限类源码分析----------------'
执行流程:
1.视图类继承APIView,通过路由匹配调用as_view方法
2.当前类中没有as_view方法,去调用父类APIView的as_view方法,取消了csrf认证,并调用View类的as_view方法
3.View类的as_view方法调用APIView类的dispatch方法
4.dispatch方法中包装了新的request,并执行APIView类的initial方法对request进行认证,频率,权限 # 源码:self.initial(request, *args, **kwargs)
5.check_permissions方法是APIView类的initial方法中对权限的实现 # 源码:self.check_permissions(request)
6.APIView类的check_permissions方法执行流程:
1.for循环视图类中的权限类列表 # 源码:for permission in self.get_permissions():
2.每次循环调用权限类对象的has_permission方法,并传入参数(权限类的对象,request,视图类的对象),返回布尔类型数据做if判断 # 源码:if not permission.has_permission(request, self):
3.如果if判断为True,权限通过
3.如果if判断为False,权限不通过,调用视图类的permission_denied方法通过反射机制给参数(request,message,code)赋值
总结:视图类的权限部署中可以部署多个权限类,都会执行
request--权限例子
from rest_framework.permissions import BasePermission
class UserTypePermission(BasePermission):
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
request--频率
应用背景:限定用户在规定时间的访问次数,无论是否通过认证和权限
步骤:
1.新建py文件,用于编写编写频率类
2.导包:from rest_framework.throttling import BaseThrottle, SimpleRateThrottle # 建议继承SimpleRateThrottle类,封装程度高,开发效率高
3.频率类继承SimpleRateThrottle类
4.重写get_cache_key方法,传入(self, request, view)参数,暂时不用写方法逻辑 # 例子: def get_cache_key(self, request, view):
5.返回一个频率限制的唯一标志,比如用户id,ip地址 # 例子:return request.META.get('REMOTE_ADDR'):返回客户端的ip地址
6.添加scope属性 # 例子:scope = 'luffy'
7.到app的settings文件中做scope属性对应,定义频率
步骤:
1.在REST_FRAMEWORK属性中添加DEFAULT_THROTTLE_RATES属性,对应数据类型为字典
2.DEFAULT_THROTTLE_RATES属性中添加键值对('scope对应数据': '频率' )
例子:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'luffy': '3/m' # 3/h 3/s 3/d
}
}
频率类的部署:
1.全局配置:
步骤:
1.在app的settings.py文件中修改REST_FRAMEWORK属性,添加DEFAULT_THROTTLE_CLASSES属性
2.DEFAULT_THROTTLE_CLASSES属性对应数据为列表套类名
例子:
REST_FRAMEWORK={
'DEFAULT_THROTTLE_CLASSES':['app01.throttling.MyThrottling'],
}
2.局部部署:
步骤:
1.在视图类的py文件中导入频率类
2.在需要频率的视图类中添加throttle_classes属性
3.throttle_classes属性对应数据类型为列表,列表元素为频率类 # 例子:throttle_classes = [MyThrottling,]
3.局部禁用:
应用背景:在全局部署频率的情况下,对单独视图类解放频率
步骤:
1.在需要解放频率的视图类中添加throttle_classes属性
2.令throttle_classes属性等于空列表 # 例子:throttle_classes = []
'--------------频率类源码分析----------------'
执行流程: # 继承SimpleRateThrottle类版本
1.视图类继承APIView,通过路由匹配调用as_view方法
2.当前类中没有as_view方法,去调用父类APIView的as_view方法,取消了csrf认证,并调用View类的as_view方法
3.View类的as_view方法调用APIView类的dispatch方法
4.dispatch方法中包装了新的request,并执行APIView类的initial方法对request进行认证,频率,权限 # 源码:self.initial(request, *args, **kwargs)
5.check_throttles方法是APIView类的initial方法中对频率的实现 # 源码:self.check_throttles(request)
6.APIView类的check_throttles方法执行流程:
1.for循环视图类中的频率类列表 # 源码:for throttle in self.get_throttles():
2.1 每次循环调用SimpleRateThrottle类的allow_request方法(因为当前频率类没有重写allow_request方法),并传入参数(SimpleRateThrottle类的对象,request,视图类的对象),返回布尔类型数据做if判断 # 源码:if not throttle.allow_request(request, self):
'---SimpleRateThrottle类的allow_request方法源码---':
1.先对self.rate做判断,如果为空,返回True,频率结束(不做频率校验)
1.1 self.rate先判断当前频率对象是否有'rate'属性,如果有,执行1.3;如果没有,执行get_rate方法
1.2 get_rate方法属于SimpleRateThrottle类方法,反射'scope'属性并做判断,如果没值,抛异常;如果有值,则返回self.THROTTLE_RATES[self.scope]
1.2.1 self.THROTTLE_RATES属性为settings文件中REST_FRAMEWORK属性里配置的'DEFAULT_THROTTLE_CLASSES'对应的数据列表
1.2.2 让后通过字典将scope属性对应的value值取出来,返回出去,再由步骤1.1的self.rate接收
1.3 调用SimpleRateThrottle类的parse_rate方法对self.rate数据进行切割,加工返回一个元组(num_requests, duration)在解压赋值给self.num_requests,self.duration
1.3.1 判断rate是否为空,如果为空,返回空元组
1.3.2 通过'/'进行切割,解压赋值给num, period
1.3.3 将num转为整型赋予num_requests
1.3.4 根据给定字典拿取period首字母对应的数据,赋予duration # 字典:{'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
1.3.5 将元组(num_requests, duration)返回
2.调用在频率类重写的get_cache_key方法,将返回结果赋给self.key,并做判断,如果self.key为空,返回True,频率结束(不做频率校验)
3.从缓存中拿取访问时间列表,赋予self.history,如果缓存中没有,则让self.history为空列表
4.将当前时间赋予self.now
5.循环判断访问时间列表self.history中是否有超过了频率校验规定时间的数据,如果有,将它删除
6.判断循环完后的self.history长度是否大于等于规定访问次数self.num_requests
7.如果大于等于,返回False,频率校验结束
8.如果小于,返回True,频率校验结束
2.2 如果继承BaseThrottle类的频率类,则需要重写allow_request方法,for循环每次循环便会调用频率类重写的allow_request方法,进行频率验证
'---自定义频率类的allow_request方法(见下方)---'
3.如果if判断为True,频率达标
3.如果if判断为False,权限不达标,调用throttle_durations.append(throttle.wait())执行频率限制
总结:视图类的权限部署中可以部署多个权限类,都会执行
request--频率例子
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
class MyThrottling(SimpleRateThrottle): # 我们继承SimpleRateThrottle去写,而不是继承BaseThrottle去写
scope = 'luffy'
def get_cache_key(self, request, view):
return request.META.get('REMOTE_ADDR')
自定义频率类
应用背景:我们想自己编辑频率逻辑,不借用SimpleRateThrottle类
步骤:
1.新建py文件,用于编写编写频率类
2.导包:from rest_framework.throttling import BaseThrottle # 继承与BaseThrottle类
3.重写allow_request方法,写频率校验逻辑
1.取出访问者的唯一标识 # 例子:取出访问者ip地址--ip = request.META.get('REMOTE_ADDR')
2.取出当前时间
3.定义新的类属性VISIT_RECORD,用于存放用户访问记录,数据类型为字典套列表
4.判断当前用户是否存在在VISIT_RECORD属性中
5.如果存在,添加记录并直接返回True,不走下面逻辑
6.如果不存在,则从VISIT_RECORD属性中取出用户对应的访问记录数据列表,赋予self.history # self.history在对象初始化配置--def __init__(self):self.history = None
7.循环访问记录列表,如果数据符合频率限制要求,就将其删除
8.判断self.history长度是否超过频率限制次数,超过返回False;未超过,想self.history访问记录数据列表头部插入当前访问时间,并返回True
4.重写wait方法,用于前端提示用户待访问时间
代码演示:
from rest_framework.throttling import BaseThrottle
class MyThrottle(BaseThrottle):
VISIT_RECORD = {} # 存放用户访问记录,例子:{ip1:[时间1,时间2],ip2:[时间1,时间2],}
def __init__(self):
self.history = None
def allow_request(self, request, view):
ip = request.META.get('REMOTE_ADDR')
import time
ctime = time.time() # 取出当前时间
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip] = [ctime, ]
return True
self.history = self.VISIT_RECORD.get(ip)
while self.history and -ctime + self.history[-1] < 60: #循环判断当前ip的时间列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,循环结束后,剩下的都是1分钟以后访问的时间
self.history.pop()
if len(self.history) < 3: # 当列表小于3,说明一分钟以内访问不足三次,插入到列表第一个位置,返回True;当大于等于3,说明一分钟内访问超过三次,返回False验证失败
self.history.insert(0, ctime)
return True
else:
return False
def wait(self):
import time
ctime = time.time()
return 60 - (ctime - self.history[-1])
内置认证类,权限类,频率类
内置认证类: # 来自rest_framework.authentication
1.BasicAuthentication # 基础认证类
2.RemoteUserAuthentication
3.SessionAuthentication # session认证
4.TokenAuthentication # jwt认证
'---都建议自己写---'
内置权限类: # 来自rest_framework.persmissions
1.BasePermission # 基础权限类
2.AllowAny:允许所有用户通过,等于没有
3.IsAuthenticated:是否登录,登录了才有权限(应用背景:认证类不抛异常)
4.IsAdminUser:是否是管理员,管理员才有权限
内置频率类: # 来自rest_framework.throttling
1.BaseThrottle # 基础频率类
2.SimpleRateThrottle # 常用
3.AnonRateThrottle:用ip做用户限制,只需要配置settings.py
例子:
'DEFAULT_THROTTLE_RATES': {'anon': '3/m',}
4.UserRateThrottle:用id做用户限制,只需要配置settings.py
例子:
'DEFAULT_THROTTLE_RATES': {'user': '3/m',}
鸭子类型
写实解释:走路像鸭子,说话像鸭子,它就是鸭子
python解释:面向对象中,子类不需要显示的继承某个类,只要这个类有某个类的相同方法和属性,那我就属于这个类
java解释:多态
问题:python鸭子类型方法容易写错
解决办法:
1.用abc模块,装饰后,必须重写方法,不重写就报错
2.父类中写这个方法,但没有具体实现,直接抛异常 # 例子:drf源码中使用
战略补充(模型类choices参数+request补充+settings文件导包)
模型类choices参数: # IntegerField方法
功能:元组套元组数据类型,将字段数据作为元组的位序实现选择功能
例子:uer_type=models.IntegerField(choices=((1,超级用户),(2,贵宾用户),(3,普通用户))
request补充:
get_字段名_display方法:
前提:当前字段参数用有choices属性
功能:实现choices属性元组数据的映射
settings.py文件导包问题:
不要再settings.py文件中导包,会报错,因为加载顺序问题