DRF 08 三个认证源码解析
权限的使用:
写的权限不一样:
ACL : 访问控制列表
rbac: 公司内部系统,基于角色的访问控制
abac:rbac升级版,加了属性认证
Django-Vue-admin 开源
认证,权限,频率源码分析
权限类的执行源码
# 权限的源码执行流程
首先我们会谢一个权限类,局部使用,配置在视图类中,就会执行权限类的has_permission方法,完成权限校验
之前读过drf的APIView的源码,知道在执行视图类方法之前就会执行3大认证 ---- dispatch方法中的497行左右
self.initial(request, *args, **kwargs)
# APIView 类的399行左右:
def initial(self, request, *args, **kwargs):
# 功能注解:在调用方法处理程序之前,运行任何需要发生的事情。
self.format_kwarg = self.get_format_suffix(**kwargs)
# 这里写的够解析的编码,版本的控制
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 确保传入请求是允许的
# 认证组件的执行位置
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
# APIView 的326 行 (权限组件)
def check_permissions(self, request):
"""
检查请求是否应该被允许。
如果请求不被允许,则引发适当的异常。
"""
主要执行的就是:get_permissions():
for permission in self.get_permissions():
# 这里的permission 就是我们配置在权限类上权限类的对象。
# 权限类的对象,执行has_permissons(),这就是我们为什么要重写权限类的hans_permissions方法的地方就是这
# 这里的self指的是我们自己写的视图类的对象view
if not permission.has_permission(request, self): # (self, request, view)
# 这里就是我们返回的值 False 就走到了这里,就没有权限
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
ps:如果我们写了多个权限类,执行到这里就结束了,因为,第一个权限已经认证失败就没必要执行其他的权限类了
# APIView的274 行:get_permissions(self)
def get_permissions(self):
"""注解:实例化并返回此视图所需的权限列表"""
return [permission() for permission in self.permission_classes]
# self.permission_classes 是我们自己配置在视图类的权限列表,里面的一个个权限类,都是没有加括号的
# rmission_classes = [CommonPermissions]
permission()=CommonPermissions() 本质 就是返回了权限类的的对象,放到了列表中
PS: 如果视图类上不配置权限类,就会执行配置文件中的
class APIView(View):
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
先使用自己的权限,没有就执行配置文件中,最后在是内置中的,
(其实内置中的那个其实就是在项目配置中的替换了,项目配置中没有就直接使用内置的配置文件)字典的替换
总结:
- 执行是在APIView的dispatch方法中执行的三大认证
- dispatch中有一个initial方法,
- initial方法的倒数第二行self.check_permissions(request)
- self.check_permissions(request)里面取出配置在视图类上的权限类,实例化得到对象,一个个执行对象的has_permissons方法
- 如果执行的结果返回的是False就直接结束,不在继续往下执行,权限就不认证通过
认证源码分析
根据上面的代码,我们找到APIView的316 行代码:
def perform_authentication(self, request):
"""对传入请求执行身份验证。"""
request.user # 这是一个方法,只是它包装成了数据属性
# 接下来就进入 Request 类中的def user(slef): 中 219行
@property
def user(self):
"""返回经过身份验证的与当前请求关联的用户通过提供给请求的身份验证类。 """
if not hasattr(self, '_user'):
with wrap_attributeerrors():
# 最终执行self._authenticate()
self._authenticate()
return self._user
# 这里的self是Request的对象,所以接下来找_authenticate()方法
# 这个authenticate就是Request类初始化的时候,就默认参数
def _authenticate(self):
"""尝试使用每个身份验证实例验证请求"""
# self.authenticators就是我们自己写的认证类的对象,放在列表中
for authenticator in self.authenticators:
'和之前的执行差不多,都是拿到一个对象来执行一个对象的authenticate方法'
try:
#这里会返回两个值,第一个值是当前登录的用户,第二个的token,直走这一个认证类,后面就不会走了
# 这里可以返回None,会继续执行下一个认证类
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是当次请求的新的REquest的对象
# self.auth=token
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
#self.authenticators 在Request类中找
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
self.authenticators = authenticators or ()
# 流程:
什么时候会调用Request的__init__呢?
在执行APIView的dispatch上面的492 行的:equest = self.initialize_request(request, *args, **kwargs),然后执行385行,在执行
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
总结:
- 配置在视图类的认证类,会在执行视图类方法之前执行,在权限认证之前执行
- 自己写的认证类,可以返回两个值或None
- 后续可以从request.user 取出当前登录用户(前提示你要在认证类中返回)
频率源码分析
根据上面APIView的代码啊,执行到APIView的352行
def check_throttles(self, request):
throttle_durations = []
#self.get_throttles() 配置在视图类上的频率类的对象,放到列表中
# 每次取出一个频率类的对象,执行allow_request方法,如果是False,频率超了,不能再走了
# 如果是True,没有超频率,可以直接往后
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
if throttle_durations:
# Filter out `None` values which may happen in case of config / rate
# changes, see #1438
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
总结:
- 我们写的频率类:首先继承BaseThrottle,重写allow_reuqest,在内部做判断,如果超过频率了,就返回False,如果没有超频率,就返回True
自定义频率类(了解)
class SuperThrottle(BaseThrottle):
VISIT_RECORD = {}
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 自己写逻辑,判断是否超频
# (1)取出访问者ip 1
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
# (1)取出访问者ip
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 = [时间1]
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])
频率类 SimpleRateThrottle
# 写一个频率类,重写allow_reuqest方法,在里面实现频率控制
# 通过SimpleRateThrottle 找到allow_reuqest
def allow_request(self, request, view):
# 这里就是通过配置文件和scop取出 频率限制是多少,比如一分钟访问5次
if self.rate is None:
return True
# 返回了ip,就以ip做限制
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()
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()
# SimpleRateThrottle的__init__方法
def __init__(self):
if not getattr(self, 'rate', None):
# self.rate= '5、h'
self.rate = self.get_rate()
# 5 36000
self.num_requests, self.duration = self.parse_rate(self.rate)
# SimpleRateThrottle的get_rate() 方法
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:
# self.scope 是 lqz 字符串
# return '5/h'
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
# SimpleRateThrottle的parse_rate 方法
def parse_rate(self, rate):
# '5/h'
if rate is None:
return (None, None)
# num =5
# period= 'hour'
num, period = rate.split('/')
# num_requests=5
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
# (5,36000)
return (num_requests, duration)
基于APIView编写分页
# 分页功能,只有查询功能才会使用
class BookView(ViewSetMixin, APIView):
def list(self, request):
books = Book.objects.all()
# 使用步骤
# 1 实例化得到一个分页类的对象
paginator = CommonLimitOffsetPagination()
# 2 调用分页类对象的paginate_queryset方法来完成分页,返回的page是 要序列化的数据,分页好的
page = paginator.paginate_queryset(books, request, self)
if page is not None:
serializer = BookSerializer(instance=page, many=True)
# 3 返回数据,调用paginator的get_paginated_response方法
# return paginator.get_paginated_response(serializer.data)
return Response({
'total': paginator.count,
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
'results': serializer.data
})
异常处理
# 在APIView类中 到dispatch中的三大认证,属兔类的方法,如果出了异常,会被异常捕获,捕获后统一处理
# drf 内置一个函数,只要上面过程出了异常,就会执行这个函数,这个函数只处理的drf异常
主动抛出的非drf异常
程序出错了都不会被处理
我们要做的是就是无论主动抛出异常还是程序运行出错,都统一返回规定规格, 可以使用一个东西 【记录日志】
在公司中通常会使用{code:1000,'msg':'系统错误'}
# 写一个函数,内部处理异常,在配置文件配置一下就好了
from rest_framework.response import Response
from rest_framework.views import exception_handler
def common_exception_handler(exc, context):
"""
:param exc: 错误对象
:param context: 上下文,有view:这里是当前出错的视图类的对象,args和kwargs视图类方法分组出来的参数
request:当次请求的是新的request对象
只要走到这里,就要记录日志,只有出错了才会执行中二哥函数
"""
res = exception_handler(exc, context)
if res:
"""
这里有值说明返回了Response对象,没有值,说明返回的是None
如果是Response对象说明是drf的异常,已经被处理掉了
如果是None表明没有处理,就是drf的异常
"""
res = Response(data={'code': 100, 'msg': res.data.get('detail','系统错误')})
return res
else:
res=Response(data={'code':1001,'msg':'系统错误,请联系管理员'})
return res
视图类
class BookView(ViewSetMixin, APIView):
def list(self, request):
# l1=[1,2,3]
# print(l1[4])
# 这是程序自己的异常,不是drf的异常
"""
出现异常的方式:主动抛异常,和程序报错
主动抛的异常和程序报的错都不会处理,
但是drf抛的异常就会统一处理
想要实现无论是哪种报错,抛出的异常都统一处理成固定格式,---最好是能够记录日志
"""
# 主动抛drf的异常 APIException
# raise APIException('主动抛drf的异常')
# 主动抛非drf异常
# raise Exception('这是一个普通的异常')
# 配置文件中配置
REST_FRAMEWORK={
'EXCEPTION_HANDLER':
'app01.exceptions.common_exception_handler',
}