源码怎么找之rest_framework的用户认证
首先得有一点常识,比如用户认证,就是authenticate
比如一个函数,应该有返回值,
比如一个类里面的self,真的是代表本身这个类吗
再比如看到一个东西加括号,就两种情况,一种是函数,一种是类,区分函数和类,就看加括号之后的东西,是否还调用属性或者方法
还有如果你用pycharm,看源码就很方便了,看到一个属性或方法,你需要做的就是按住ctrl点进去就行了
假设你已经知道,dispatch函数是源码的入口,然后我们就可以愉快的交流了
下面演示如何使用rest_framework的用户认证功能,以及为什么要这样做
1.写路由匹配
因为是cbv视图,所以用as_view()
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^test/', views.TestView.as_view()), ]
2.CBV风格视图
from django.shortcuts import HttpResponse from rest_framework.views import APIView#导入rest_framework的试图类 class TestView(APIView):
self.dispatch #只是为了进源码 def get(self,request,*args,**kwargs): return HttpResponse('ok')
好了,最基本的框架就这样搭好了,我们开始看源码,源码的入口是在 APIView 的dispatch方法,所以我们点进去
dispatch函数:
def dispatch(self, request, *args, **kwargs):#这是APIView类的入口 """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. 和Django的dispatch类似,但是增加了额外的钩子,比如开始,结束,以及异常的处理 """ self.args = args #封装参数 self.kwargs = kwargs request = self.initialize_request(request, *args, **kwargs) #重新封装了request 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.initial初识化这两个地方
1.1先看重新封装request这个
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. 返回初始化后的request """ 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 )
1.2
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes] #从配置读取用户认证配置,然后实例化,点进这个self.authentication_classes
这个函数返回了一个列表,通过列表生成式,循环 self.authentication_classes ,然后拿到的东西实例化
tips:看到一个东西加括号,就两种情况,一种是函数,一种是类,区分函数和类,就看加括号之后的东西,是否还调用属性或者方法
1.3
class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
坏了,这下不能点了,看settings,说明这是配置信息,好了,看request看到这就走不下去了,因为不能点了,现在我们拿到的信息是:
在封装request的时候, 把一个auth()列表传到了request的参数里面,然后这个auth()是从这个self.authentication_classes 里面拿到的
现在我们可以打印一下这个 self.authentication_classes,self就是APIview 嘛,我门的视图函数就是继承了这个类啊,所以,打印一下:
拿到了这个:
[<class 'rest_framework.authentication.SessionAuthentication'>, <class 'rest_framework.authentication.BasicAuthentication'>]
这种情况应该兴奋,因为真的有东西,拿到的是个路径,然后我们就可以用这个路径拿到这两个类
并且,验证了self.authentication_classes 就是个列表,然后里面是一个一个类
如果我们自己写这个authentication_classes的话,也要写成一个列表,里面是一个个类,下面看一下这个类要怎么写
1.4
然后我们先导入一下这个路径,看一下里面写的啥,
from rest_framework.authentication import SessionAuthentication
点进去
是一个新的文件
总体看一下,这个文件有这样几个类
然后都继承了BaseAuthentication类,先看一下这个类
1.5
class BaseAuthentication(object): """ All authentication classes should extend BaseAuthentication. """ def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). """ raise NotImplementedError(".authenticate() must be overridden.") def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ pass
没有什么实质性的东西,但是规定了继承这个类的子类,必须要有这个 authenticate 方法,否则就会报错,所以我们就知道了应该怎么自己写这个类了
那就可以猜到,下面继承了这个基类的几个子类就是不同的认证方法了
看了这个基类,大概知道自己定义的类里面必须要写这样一个authenticate 方法,不写会报错,但是应该怎么写呢?需要返回什么呢,其实重点是要返回什么
我们现在才走了一半,只看了封装request这一部分,还有初始化呢,重新点进dispatch函数,找到
2.1
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) ——-执行认证
注意这里传了参数,是封装后的request
2.2
def perform_authentication(self, request): """ Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication will instead be performed lazily, the first time either `request.user` or `request.auth` is accessed. """ request.user
就执行了一个request.user,这个user应该是个方法,才能直接这么用,所以再找request里面的user方法,
2.3
@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'): self._authenticate() return self._user
果不其然,是一个加了装饰器的方法,我们的request里面没有这个_user属性,所以执行的是 self._authenticate() 这个方法
2.4
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,拿到 authenticator 并执行 authenticator 里面的 authenticate 方法,参数为request
注意, self.authenticators 这个东西在1.1中就已经拿到了,这个就是我们要写的验证的类,然后跟我们得到的结论也一样,有一个authentic 方法
2.5 看看这个方法,authentic
def authenticate(self, request): return (self.force_user, self.force_token)
哦,返回一个元组,大概就是 用户和token,现在这两行代码是如果有指定值的时候的情况,但是可以说明问题
再看看1.4里面那几个验证的子类,基本上就确认是返回一个已通过认证的用户和对应的token
这是通过认证的情况
2.6再看2.4里面,最后那个 self._not_authenticated() ,是表示未通过验证的情况
点进去
def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER() else: self.user = None if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN() else: self.auth = None
没有默认值的话,是返回None的,但是是有默认值的
点进去api_settings ,是个类
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
再点进去,就是rest_framework的配置文件
搜一下,就可以看到这条配置
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 'UNAUTHENTICATED_TOKEN': None,
默认是调用Django的匿名用户类,所以默认如果不登录,是匿名用户
还能说明一个问题,不登录也能访问,即匿名用户访问是否被允许,就可以在认证类中配置了,返回None,就是允许,抛出异常就是不允许
贴一个完整的代码
from django.shortcuts import HttpResponse from rest_framework.views import APIView#导入rest_framework的试图类 from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from . import models class CustomAuthenticate(BaseAuthentication): """ All authentication classes should extend BaseAuthentication. """ def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). """ tk = request.query_param.get('tk') #获取用户输入的token 简化写法,就直接在url中传参了 token_obj = models.Token.objects.filter(token=tk).first() if token_obj: return (token_obj.user,token_obj) raise exceptions.AuthenticationFailed('请登录') #抛出异常表示不允许匿名用户访问 # else: # return (None,None) def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ pass class TestView(APIView): authentication_classes = [CustomAuthenticate,] #应用认证配置类 def get(self,request,*args,**kwargs): return HttpResponse('ok')