Django rest framework 的認證流程(原始碼分析一)
一、基本流程舉例:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', views.HostView.as_view()), ]urls
from rest_framework.views import APIView from rest_framework.response import Response class HostView(APIView): def dispatch(self, request, *args, **kwargs):Views""" 請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET請求,響應內容') def post(self, request, *args, **kwargs):return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
二、原始碼分析:
當我們傳送請求後,執行views裡面對應的方法時,最開始執行的是dispatch方法
---------------------------------------------------------------------------------- 插敘-------------------------------------------------------------------------------------------------------------------
為什麼是diapach方法?
我們使用rest framework 框架是基於CBV做的,在url中
url(r'^hosts/', views.HostView.as_view()), url(r'^auth/', views.AuthView.as_view()), url(r'^users/', views.Userview.as_view()), url(r'^sals/', views.Salview.as_view()),
進入as_view()
APIView類中的as_view(),注意加紅程式碼
@classmethod def as_view(cls, **initkwargs): """ Store the original class on the view function. This allows us to discover information about the view when we do URL reverse lookups. Used for breadcrumb generation. """ if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet): def force_evaluation(): raise RuntimeError( 'Do not evaluate the `.queryset` attribute directly, ' 'as the result will be cached and reused between requests. ' 'Use `.all()` or call `.get_queryset()` instead.' ) cls.queryset._fetch_all = force_evaluation view = super(APIView, cls).as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. return csrf_exempt(view)
進入原生View類中
@classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view
可以看到最後返回的是
return self.dispatch(request, *args, **kwargs)
所以訪問views中的類是先執行dispatch方法,然後再用呼叫 其他方法
---------------------------------------------------------------------------------- 插敘結束-------------------------------------------------------------------------------------------------------------------
自己定義的dispatch方法(當然自己可以不定義直接應用APIView裡面的就可以了,預設就是這種):
def dispatch(self, request, *args, **kwargs): """ 請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs)
APIView中的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 #1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request ''' request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context '''
# 原來request物件,django.core.handlers.wsgi.WSGIRequest
# 現在的request物件,rest_framework.request.Request
request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: #2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs) # Get the appropriate handler method #3.據使用者提交的請求方法利用反射獲取請求方法 #http_method_names=['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] 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) #4.將處理後的response包裝 self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request
request = self.initialize_request(request, *args, **kwargs)
進入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 )
將這些東西前部都封裝在rest_framework的Resquest中,並返回她的物件request,從這以後我們呼叫的request物件不再是Django提供的request物件了,而是APIView的Resquest的物件,2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
self.initial(request, *args, **kwargs)
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. #2.1處理版本資訊 version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted #2.2處理認證資訊 self.perform_authentication(request) #2.3處理許可權資訊 self.check_permissions(request) #2.4對使用者的訪問頻率進行限制 self.check_throttles(request)
3.據使用者提交的請求方法利用反射獲取請求方法,並改用請求方法實現其具體功能
4.將處理後的response進行包裝
三、處理認證的具體分析
當diapatch方法進行到第一步時,我們呼叫了initialize_request方法將request等封裝在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 )
authenticators=self.get_authenticators() #用於使用者認證 是一個由authentication物件組成的列表
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes]
authentication_classes為一個authentication類組成的列表,他預設是呼叫
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
當然我們一般是自己定義或者配置到settings中,至此我們的得到authentication物件的列表,其封裝在Request物件中,
接的執行第二步
#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs)
#2.2處理認證資訊 self.perform_authentication(request)
檢視perform_authentication的原始碼如下
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
其呼叫了rest_framework中Request的user方法(這個方法肯定別@property裝飾,不然的話不可能直接不加括號的呼叫)
from rest_framework.request import 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. """ #判斷當前類中是否有已經認證過的user if not hasattr(self, '_user'): #沒有認證則去認證 self._authenticate() #認證過了直接返回 return self._user
注意:user中的self代表的是request物件
沒認證的話執行 呼叫Request類中的_authenticate()方法
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ #遍歷request物件中封裝的Authentication物件 for authenticator in self.authenticators: try: #呼叫Authentication物件中的authenticate方法,必須要有這個方法不然丟擲異常 #當然Authentication類一般有我們自己定義,實現這個方法就可以了 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()
認證例項:
區域性認證:直接類中定義Authentication類
from django.db import models # Create your models here. class Userinfo(models.Model): name=models.CharField(max_length=32,verbose_name='使用者名稱') pwd=models.CharField(max_length=32,verbose_name='密碼') token=models.CharField(max_length=64,null=True) def __str__(self): return self.name
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', views.HostView.as_view()), url(r'^auth/', views.AuthView.as_view()), ]
import json import hashlib import time from django.shortcuts import render, HttpResponse from rest_framework.views import APIView from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import BaseAuthentication from rest_framework.request import Request from rest_framework.exceptions import APIException from rest_framework.response import Response from app01 import models # Create your views here. class MyAuthentication(object): def authenticate(self, request): token = request.query_params.get('token') user=models.Userinfo.objects.filter(token=token).first() if user: return (user.name, 'aaaaaa') raise APIException('認證失敗') class AuthView(APIView): # 設定為空標識不用認證 authentication_classes = [] def get(self, request, *args, **kwargs): dic = {'code': 1000, 'msg': ''} pwd = request.query_params.get('pwd') name = request.query_params.get('name') user = models.Userinfo.objects.filter(name=name, pwd=pwd).first() if not user: dic['msg'] = '使用者名稱或密碼錯誤' dic['code'] = 1001 return Response(dic) t = time.time() key = '%s|%s' % (name, t) m = hashlib.md5() m.update(key.encode('utf-8')) token = m.hexdigest() user.token = token user.save() dic['token'] = token dic['msg'] = '登陸成功' return Response(dic) class HostView(APIView): authentication_classes=[MyAuthentication,] def dispatch(self, request, *args, **kwargs): """ 請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
全域性認證;在settings中配置
from django.db import models # Create your models here. class Userinfo(models.Model): name=models.CharField(max_length=32,verbose_name='使用者名稱') pwd=models.CharField(max_length=32,verbose_name='密碼') token=models.CharField(max_length=64,null=True) def __str__(self): return self.namemodels
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', views.HostView.as_view()), url(r'^auth/', views.AuthView.as_view()), ]urls
from rest_framework.exceptions import APIException from rest_framework.response import Response from rest_framework.authentication import BaseAuthentication from app01 import models class MyAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get('token') user=models.Userinfo.objects.filter(token=token).first() if user: return (user.name, 'aaaaaa') raise APIException('認證失敗')utils
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, "DEFAULT_AUTHENTICATION_CLASSES": [ "app01.utils.MyAuthentication", ], }settings中
一、基本流程舉例:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', views.HostView.as_view()), ]urls
from rest_framework.views import APIView from rest_framework.response import Response class HostView(APIView): def dispatch(self, request, *args, **kwargs): """ 請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')Views
二、原始碼分析:
當我們傳送請求後,執行views裡面對應的方法時,最開始執行的是dispatch方法
---------------------------------------------------------------------------------- 插敘-------------------------------------------------------------------------------------------------------------------
為什麼是diapach方法?
我們使用rest framework 框架是基於CBV做的,在url中
url(r'^hosts/', views.HostView.as_view()), url(r'^auth/', views.AuthView.as_view()), url(r'^users/', views.Userview.as_view()), url(r'^sals/', views.Salview.as_view()),
進入as_view()
APIView類中的as_view(),注意加紅程式碼
@classmethod def as_view(cls, **initkwargs): """ Store the original class on the view function. This allows us to discover information about the view when we do URL reverse lookups. Used for breadcrumb generation. """ if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet): def force_evaluation(): raise RuntimeError( 'Do not evaluate the `.queryset` attribute directly, ' 'as the result will be cached and reused between requests. ' 'Use `.all()` or call `.get_queryset()` instead.' ) cls.queryset._fetch_all = force_evaluation view = super(APIView, cls).as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. return csrf_exempt(view)
進入原生View類中
@classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view
可以看到最後返回的是
return self.dispatch(request, *args, **kwargs)
所以訪問views中的類是先執行dispatch方法,然後再用呼叫 其他方法
---------------------------------------------------------------------------------- 插敘結束-------------------------------------------------------------------------------------------------------------------
自己定義的dispatch方法(當然自己可以不定義直接應用APIView裡面的就可以了,預設就是這種):
def dispatch(self, request, *args, **kwargs): """ 請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs)
APIView中的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 #1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request ''' request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context '''
# 原來request物件,django.core.handlers.wsgi.WSGIRequest
# 現在的request物件,rest_framework.request.Request
request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: #2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs) # Get the appropriate handler method #3.據使用者提交的請求方法利用反射獲取請求方法 #http_method_names=['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] 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) #4.將處理後的response包裝 self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request
request = self.initialize_request(request, *args, **kwargs)
進入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 )
將這些東西前部都封裝在rest_framework的Resquest中,並返回她的物件request,從這以後我們呼叫的request物件不再是Django提供的request物件了,而是APIView的Resquest的物件,2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
self.initial(request, *args, **kwargs)
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. #2.1處理版本資訊 version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted #2.2處理認證資訊 self.perform_authentication(request) #2.3處理許可權資訊 self.check_permissions(request) #2.4對使用者的訪問頻率進行限制 self.check_throttles(request)
3.據使用者提交的請求方法利用反射獲取請求方法,並改用請求方法實現其具體功能
4.將處理後的response進行包裝
三、處理認證的具體分析
當diapatch方法進行到第一步時,我們呼叫了initialize_request方法將request等封裝在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 )
authenticators=self.get_authenticators() #用於使用者認證 是一個由authentication物件組成的列表
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes]
authentication_classes為一個authentication類組成的列表,他預設是呼叫
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
當然我們一般是自己定義或者配置到settings中,至此我們的得到authentication物件的列表,其封裝在Request物件中,
接的執行第二步
#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs)
#2.2處理認證資訊 self.perform_authentication(request)
檢視perform_authentication的原始碼如下
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
其呼叫了rest_framework中Request的user方法(這個方法肯定別@property裝飾,不然的話不可能直接不加括號的呼叫)
from rest_framework.request import 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. """ #判斷當前類中是否有已經認證過的user if not hasattr(self, '_user'): #沒有認證則去認證 self._authenticate() #認證過了直接返回 return self._user
注意:user中的self代表的是request物件
沒認證的話執行 呼叫Request類中的_authenticate()方法
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ #遍歷request物件中封裝的Authentication物件 for authenticator in self.authenticators: try: #呼叫Authentication物件中的authenticate方法,必須要有這個方法不然丟擲異常 #當然Authentication類一般有我們自己定義,實現這個方法就可以了 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()
認證例項:
區域性認證:直接類中定義Authentication類
from django.db import models # Create your models here. class Userinfo(models.Model): name=models.CharField(max_length=32,verbose_name='使用者名稱') pwd=models.CharField(max_length=32,verbose_name='密碼') token=models.CharField(max_length=64,null=True) def __str__(self): return self.name
urlpatterns = [ url(r'^adm