一、简介
本篇文章主要介绍drf认证组件的快速使用,并从源码角度分析drf认证组件的调用过程
二、快速使用
①首先自定义一个认证组件auth.py,该组件可以放在项目的任何位置,除了views.py中,如果写在views.py中会出现循环引用的问题,具体原因如下,全局的认证组件放在了settings.py中
REST_FRAMEWORK={
'UNAUTHENTICATED_USER': None,
'DEFAULT_AUTHENTICATION_CLASSES': ['自定义认证组件的路径', ],
}
如由于views.py继承了APIView类,而在APIView类中定义了 authentication_classes
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
当读取settings.py中的自定义组件路径时,便会加载views.py,从而通过APIView读取到上面这段代码,又会跳到settings.py中读取'DEFAULT_AUTHENTICATION_CLASSES',如此反复导致了循环引用,因此需要将自定义认证组件放在除view.py的任何位置即可
我创建了一个ext目录,将auth.py写在了ext目录下
因此settings.py的路径如下:
REST_FRAMEWORK={
'UNAUTHENTICATED_USER': None,
'DEFAULT_AUTHENTICATION_CLASSES': ['ext.auth.MyAuthentication', ],
}
auth.py代码如下:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
# 用户认证步骤:
# 1.读取请求传递的token
# 2.校验合法性
# 3.三种返回值
# 3.1 认证成功,返回元组(11,22)
# 11,22分别存入request.user和request.auth
# 3.2 认证失败,抛出异常 返回错误信息
# 3.3 返回None(不常用),用于多个认证类
# /xxx/xxx/?token=1231dd
# token = request._request.GET.get('token')
# drf的request源码中的query_params=_request.GET
token = request.query_params.get('token')
# 这里获取到请求传递的token后,理应校验合法性,不过这里先略去
if token:
return "user", token
# raise AuthenticationFailed("认证失败")
# 如果返回字符串,字符串会放入detail中,如果返回字典,则该字典会代替detail
raise AuthenticationFailed({"code": 0, "msg": "认证失败"})
②在不需要认证的类中定义authentication_classes为空
views.py代码
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
def get(self, request):
print(request.user)
print(request.auth)
return Response("UserView")
class LoginView(APIView):
# LoginView不需要认证
authentication_classes = []
def get(self, request):
print(request.user)
print(request.auth)
return Response("LoginView")
class OrderView(APIView):
def get(self, request):
self.dispatch()
return Response("OrderView")
③访问url测试api接口
访问login不需要加token
访问user如果不加token便会报错
如果加上token便可正常访问
三、源码分析
接下来介绍drf的认证组件源码,解析其是如何实现认证功能的
通过上上节CBV和FBV的学习后,我们知道实现了不同请求方式的访问是通过APIView中的dispatch的反射实现的,实际上dispatch不只做了反射这件事,其源码如下:
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
首先查看这段代码
request = self.initialize_request(request, *args, **kwargs)
大致意思是通过调用initialize_request方法将django的request封装成drf的request,那么我们查看一下initialize_request具体做了什么事,其源码如下:
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
)
返回了一个Request对象,其中authenticators=self.get_authenticators(),那么就需要看一下get_authenticators做了什么事,其源码如下:
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
返回的是一个装着对象的列表,即[对象,对象,对象],而这实例化对象读取的是self.authentication_classes,这里的self是views.py中的继承APIView的类,如LoginView,UserView等,因此首先会去类本身里找authentication_classes,如LoginView代码定义了authentication_classes为空
authentication_classes = []
如果没找到便去父类APIView中找authentication_classes,而上面提到APIView中的authentication_classes定义如下:
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
因此即是去settings中寻找api_settings.DEFAULT_AUTHENTICATION_CLASSES的定义,即
'DEFAULT_AUTHENTICATION_CLASSES': ['ext.auth.MyAuthentication', ]
现在已经明白了,传入Request对象中的authenticators参数是什么,那么接下来去看一下Request对象是如何定义的,其源码(仅__init__部分)如下:
class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
Kwargs:
- request(HttpRequest). The original request instance.
- parsers(list/tuple). The parsers to use for parsing the
request content.
- authenticators(list/tuple). The authenticators used to try
authenticating the request's user.
"""
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._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
if self.parser_context is None:
self.parser_context = {}
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
force_user = getattr(request, '_force_auth_user', None)
force_token = getattr(request, '_force_auth_token', None)
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
我们只需要关注如下这句代码:
self.authenticators = authenticators or ()
将authenticators 即[对象,对象,对象]赋值给了self.authenticators,即request.authenticators中存储的便是[对象,对象,对象]
接下来回到dispatch,往下走,dispatch源码如下:
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
走到如下代码
self.initial(request, *args, **kwargs)
调用了initial方法,那么就需要知道initial做了什么,其源码如下:
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
# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
我们需要关注这段代码
self.perform_authentication(request)
这段代码调用了perform_authentication方法,那么就需要知道perform_authentication做了什么,其源码如下:
def perform_authentication(self, request):
request.user
这里返回了一个request.user,我们已经知道此时的request已经是drf的request,request来自于Request类,那么我们需要去Request类中寻找user的定义,其源码如下:
@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
@property的作用即是当调用该方法的时候,不需要加(),因此上面的request.user实际上是在调用该user方法,其中user方法实现了如下功能,首先是判断在request中是否有_user属性,如果没有则调用_authenticate方法,那么我们先去看一下_authenticate实现了什么功能,其源码如下:
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
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
self._not_authenticated()
它这里做的第一步便是循环self.authenticators,前面提到self.authenticators是[对象,对象,对象],即我们自定义的认证组件,然后就是调用自定义认证组件的authenticate方法,这里回顾一下我们写的自定义组件,其源码如下:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
# 用户认证步骤:
# 1.读取请求传递的token
# 2.校验合法性
# 3.三种返回值
# 3.1 认证成功,返回元组(11,22)
# 11,22分别存入request.user和request.auth
# 3.2 认证失败,抛出异常 返回错误信息
# 3.3 返回None(不常用),用于多个认证类
# /xxx/xxx/?token=1231dd
# token = request._request.GET.get('token')
# drf的request源码中的query_params=_request.GET
token = request.query_params.get('token')
# 这里获取到请求传递的token后,理应校验合法性,不过这里先略去
if token:
return "user", token
# raise AuthenticationFailed("认证失败")
# 如果返回字符串,字符串会放入detail中,如果返回字典,则该字典会代替detail
raise AuthenticationFailed({"code": 0, "msg": "认证失败"})
因此便是将"user"和 token作为返回值赋值给了user_auth_tuple,然后便是判定user_auth_tuple 是否为空,不为空即将user_auth_tuple赋值给self.user和self.auth,注意这里self.user和self.auth仍然是方法而不是属性,以self.user为例,其源码为:
@user.setter
def user(self, value):
"""
Sets the user on the current request. This is necessary to maintain
compatibility with django.contrib.auth where the user property is
set in the login and logout functions.
Note that we also set the user on Django's underlying `HttpRequest`
instance, ensuring that it is available to any middleware in the stack.
"""
self._user = value
self._request.user = value
与上面的@property不同,这里的@user.setter是赋值方法,里面的self._user = value,self._request.user = value就是将user_auth_tuple中的第一个值"user"分别赋值给了他们,前者self._user有值了之后便可在@property的那段代码那里通过if语句判断,直接返回self._user,而后者的值则更为常用,当我们要调取当前登录的用户时,即可用request._request.user获取,而在上一节request对象的学习中,我们知道在Request类中定义了__getattr__方法,因此我们可以直接使用request.user调取
以上便是drf的认证组件的所有源码分析