DRF - 认证,权限,频率源码分析、异常统一处理
1.原生django的cookie+session认证底层原理
2.断点调试
程序是debug模式运行,可以在任意位置停下,查看当前情况下变量数据的变化情况
3.认证,权限,频率源码分析
请求来了,进行路由匹配,匹配成功后,执行可以执行对象,
1.权限的源码执行流程
我们自定制了一个权限类,通过配置视图类中的permission_classes
局部使用时,就会执行权限类的has_permission
方法,完成权限校验
(1)APIView的dispatch
中 - 3大认证
APIView的 dispatch 方法,在执行视图类的方法之前,会先执行3大认证【认证、权限、频率】的校验。
也就是在497行左右,在self.initial
中进行
def dispatch(self, request, *args, **kwargs):
...
try:
self.initial(request, *args, **kwargs)
(2)APIView的 initial
- 3大认证
在 【initial】 方法中,执行3大认证
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
# 能够解析的编码、版本控制
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
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)
(3)执行APIView中的check_permissions
方法 - 执行登录认证
-
self.get_permissions()
是去执行我们在视图类上配置的【权限类列表】==>[CommonPermission(),] -
permission
是我们写的【权限类的对象】 -
在没有权限的情况下,执行
permission.has_permission(request, self)
,self
是【视图类的对象】,就是自己写的权限类的has_permission
的view
参数 -
如果return的是False,就会走
self.permission_denied(...)
,返回message
和code
结束 -
所以如果配置了多个权限类,前面的没有通过则不会执行下一个权限类了
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
# self.get_permissions()
# permission是我们写的权限类的对象,self.get_permissions()
for permission in self.get_permissions():
# 没有权限的情况下执行这个
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
self是视图类的对象,是自己写的has_permission的view参数
(4)APIView的get_permissions
方法
self.permission_classes
是我们自己在视图类上的列表,里面是一个个的权限类
-
self.permission_classes
-
permission_classes = [CommonPermission]
-
[CommonPermission()] 本质返回了权限类的对象,放在列表中
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
# 1 self.permission_classes
# 2 permission_classes = [CommonPermission]
# 3 [CommonPermission()] 本质返回了权限类的对象,放在列表中
return [permission() for permission in self.permission_classes]
总结执行流程:
1.局部配置权限类时
权限类 -- APIView --- dispatch --- initial --- check_permissions(request)
从中取出配置在视图类上的权限类,实例化得到对象,一个个执行对象的has_permission方法,如果返回False就直接结束,不在往下继续执行了,权限认证结束
2.局部没有配置权限类,则会使用全局的配置
如果视图类上不配置权限类:permission_classes = [CommonPermission
-->会使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES
优先级: 局部>全局>默认
优先使用项目配置,其次使用默认配置【当全局中有的时候,会替换默认配置】
2.认证
(1)APIView中dispatch
方法【同权限】
APIView的 dispatch
方法,在执行视图类的方法之前,会先执行3大认证【认证、权限、频率】的校验。
也就是在497行左右,在self.initial
中进行
(2)APIView的 initial
- 3大认证
在initial
方法中,执行3大认证的认证
# 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)
(3)APIView的 perform_authentication
方法 - 执行权限认证
request.user
是伪装成属性的方法,这里的request是drf包装了的新的request对象,所以我们去读drf的Request源码
def perform_authentication(self, request):
request.user
(4)Request类的user
方法
user方法通过伪装可以以属性的方式使用
其中执行了 self._authenticate()
方法
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
(5)Request类的_authenticate
方法
self
是Request的对象,self.authenticators
是我们配置在视图类中放在列表中的一个个认证类对象,是在Request类初始化的时候,传入的- 返回了两个值,第一个是【当前用户】,第二个是【token】,当返回这两个值的时候后面的认证类就不继续进行了
-
其中解压赋值
self.user=当前登录用户
,self.auth=token
-
self
是当次请求的【新的Request的对象】,所以登录认证完成后,通过request.user
即可获得当前登录的用户对象
- 如果返回的是None,会继续执行下一个认证类
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
# 1 self.authenticators 我们配置在视图类上认证类的一个个对象,放到列表中
# Request类初始化的时候,传入的
for authenticator in self.authenticators:
try:
# 2 返回user_auth_tuple
user_auth_tuple = authenticator.authenticate(self)
# APIException是drf自己定义的一个异常
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
"解压赋值"
# self.user=当前登录用户,self是当次请求的新的Request的对象
# self.auth=token
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return self._not_authenticated()
(6) Request 的__init__
传入authenticators
- 通过上面的
self.authenticators
,我们发现Request类在初始化的时候,自动传入了一个authenticators
- 就是在APIView 的
dispatch
中的492行,保存了产生了新的request对象
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
.format(request.__class__.__module__, request.__class__.__name__)
)
self.authenticators = authenticators or ()
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
(7)APIView 的 dispatch
APIView 的 dispatch
中的492行,保存了产生了新的request对象,其中执行了initialize_request
方法,进行了Request的初始化
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
self.request = request
(8)APIView 的 initialize_request
在initialize_request
中,执行了authenticators=self.get_authenticators()
,也就是初始化传入了authenticators
,传入了认证类对象
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
# 就是这里传入的认证类对象
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
(9)APIView 的 get_authenticators
self.authentication_classes
视图类中配置的一个个的认证类的列表,如果没配,使用配置文件中或者内置配置文件中认证类
[auth() for auth in self.authentication_classes]
通过生成式,生成认证类对象以供校验
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]
总结
1.配置在视图类上的认证类,会在执行视图类方法之前执行,并且在权限认证之前执行
2.自己定制的认证类,可以返回两个值则直接结束,返回None则继续执行下一个认证类
3.认证类中返回了当前登录用户,则可以在后续权限中可以通过request.user拿到当前登录用户
3.频率
(1)APIView中dispatch
方法
【同上】
(2)APIView的 initial
- 3大认证
【同上】
(3)APIView 的 check_throttles
方法 - 执行频率认证
self.get_throttles()
就是产生我们在视图类中局部配置的频率类的对象,并将对象放在列表中
每次取出一个频率类的对象,执行allow_request
方法,如果是False,频率超了不再继续执行;是True则可以往后执行
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
throttle_durations = []
# self.get_throttles() 就是产生我们在视图类中局部配置的频率类的对象,并将对象放在列表中
# 每次取出一个频率类的对象,执行allow_request方法,如果是False,频率超了,不能再走了
for throttle in self.get_throttles():
# 如果是True,那么没有超出频率,继续执行
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_request
在内部判断,如果超频了,就返回False,不允许访问;如果没超频率,就返回True运行继续访问
4.继承BaseThrottle
自定义编写频率类
关于频率限制的思路都是一致的,在实际项目中,多将认证、权限、频率的限制放在中间件中
1.判断是否超频的逻辑
(1)取出访问者ip
(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
2.代码演示 - 通过模仿SimpleRateThrottle
VISIT_RECORD
为存放ip地址对应访问时间的字典,key
为ip,value
为列表__init__
初始化一个存放allow_request
方法是,继承BaseThrottle
需要重写的类,其中构建判断频率的逻辑,返回True为运行访问,返回False为限制访问wait
方法返回一个时间,当有该方法存在时候,会显示限制访问的剩余时间
class SuperThrottle(BaseThrottle):
# 定义一个访问字典,存放ip地址对应的每次的访问时间
VISIT_RECORD = {}
# 定义一个记录登录时间的列表,初始化为None
def __init__(self):
self.history = None
def allow_request(self, request, view):
# (1)取出访问者ip
ip = request.META.get('REMOTE_ADDR')
# 导入时间
import time
ctime = time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
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,顺利通过
if len(self.history) < 3:
self.history.insert(0, ctime)
return True
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
else:
return False
# wait 方法 清晰等待时间
def wait(self):
"""
Returns the recommended next request time in seconds.
返回一个单位为秒的建议等待时间
"""
import time
ctime = time.time()
return 60 - (ctime - self.history[-1])
5.SimpleRateThrottle源码分析
写一个频率类继承SimpleRateThrottle
,其中的allow_request
方法,来控制频率
def allow_request(self, request, view):
# self.rate是频率的限制,没有self.rate则没有限制,直接返回None
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()
self.rate
是频率的限制,没有self.rate则没有限制,直接返回None,其通过配置文件中的scope参数来获取频率,没有scope的话则直接抛出异常
``THROTTLE_RATES是在配置文件中配置的频率限制,
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES` 5/h
-
SimpleRateThrottle
的__init__
方法得到
self.rate
后,传给self.parse_rate
方法,并解压复制给self.num_requests
频率限制的次数,self.duration
频率限制的时间
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
-
SimpleRateThrottle
的parse_rate
方法通过切割解压赋值,得到
self.num_requests = num
频率限制的次数,self.duration = duration
频率限制的时间
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)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
- 继续
SimpleRateThrottle
的allow_request
方法
def allow_request(self, request, view):
...
# 获取缓存中的 访问时间列表
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()
3.基于APIView来写分页功能
1.ListModelMixin
的源码分析
(1)ListModelMixin
中的list方法
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# 1 关于分页的部分,通过self.paginate_queryset 产生一个分页类对象,
# self.paginate_queryset就是`GenericAPIView`中的`paginate_queryset`方法
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
(2)GenericAPIView
中的paginate_queryset
方法
关于分页的部分,通过self.paginate_queryset
也就是 GenericAPIView
中的paginate_queryset
方法产生一个分页类对象,并返回了 分页类对象
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
(3)list方法中
返回 分页类对象,将分页类对象传给 【序列化对象】serializer = self.get_serializer(page, many=True)
然后返回一个Response对象 self.get_paginated_response(serializer.data)
(4)PageNumberPagination
的get_paginated_response
方法
self.get_paginated_response(serializer.data)
就是PageNumberPagination
的get_paginated_response
方法
def get_paginated_response(self, data):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data)
]))
2.代码实现
class BookView(ViewSetMixin, APIView):
def list(self, request):
books = Book.objects.all()
# 1。获取paginator 也就是分页类对象
paginator = CommonPageNumberPagination()
# 2。paginate_queryset(self, queryset, request, view=None)
# queryset就是我们传入的分页的模型对象,view在这里就是self
page = paginator.paginate_queryset(books, request, self)
if page is not None:
# 3 get_serializer(queryset, many=True) 序列化类就是BookSerializer
# 这里的queryset对象指的是分页类对象 page
serializer = BookSerializer(instance=page, many=True)
# return paginator.get_paginated_response(serializer.data)
# 4 PageNumberPagination的get_paginated_response方法
return Response({'count': paginator.page_size,
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
'results': serializer.data})
4.异常处理
1.drf的异常分类
在drf中异常分为两种:(1)drf的异常 (2)非drf的异常
其中,【drf的异常】会被【drf内置的函数】捕获,比如【三大认证,视图类的方法】出现了异常,就会被【内置函数捕获后统一处理】
但是,【非drf的异常】如:程序出错、主动抛出的python异常都不会被处理
所以,在实际工作中无论是否是drf 的异常都应该以统一格式来返回,并记录日志
如
{code:999,'msg':'系统错误,请联系系统管理员'}
DRF的异常处理为:
APIException
Django的异常处理为:
Http404
和PermissionDenied
2.异常统一处理
(1)异常处理函数exception_handler
drf中的 处理异常APIException
的函数
如果是drf 的异常则返回Request对象,否则返回None
from rest_framework.views import exception_handler
(2)代码演示
excepions.py中
from rest_framework.views import exception_handler
from rest_framework.response import Response
def my_exception_handler(exc, context):
"""
自定义的统一处理drf异常和非drf异常格式的函数
:param exc: 错误对象
:param context: 上下文,包含view,request
:return: 信息字典【响应码,错误信息...】
Response(data, status=exc.status_code, headers=headers)
data是信息字典【响应码,错误信息...】
"""
# 1.通过exception_handler函数捕获异常,由于该函数捕获的不是drf的异常时,返回None
res = exception_handler(exc, context)
# 2. 判断返回值是否是none
if res:
# 3 有值则是drf 的异常,res.data.get('detail')为drf的异常信息
res = Response(data={'code': 111, 'msg': res.data.get('detail', '请联系管理员')})
else:
res = Response(data={'code': 222, 'msg': '非drf错误'})
return res
settings.py中
配置EXCEPTION_HANDLER
异常处理函数使用自己编写的
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exceptions.my_exception_handler',
}
- drf的错误
- 非drf的错误