Django rest framework 源碼分析 (1)----認證

1、基礎

django 2.0官方文檔python

https://docs.djangoproject.com/en/2.0/

安裝

pip3 install djangorestframework

 

假如咱們想實現用戶必須是登錄後才能訪問的需求,利用restframework該如何的去實現,具體的源碼流程又是怎麼樣的呢

爲了有一個清晰的認識,先直接上代碼,有一個清晰的認識,在剖析源碼流程

 

首先先建立一個應用  django

python manage.py startapp app01

 

在應用 app01.views.py 下 的視圖函數的代碼以下json

from rest_framework.views import APIViewfrom rest_framework import exceptions
from rest_framework.request import Request

class MyAuthentication(object):
    def authenticate(self,request):
        token = request._request.GET.get('token')
        # 獲取用戶名和密碼,去數據校驗
        if not token:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        return ("dog",None)

    def authenticate_header(self,val):
        pass

class DogView(APIView):
    authentication_classes = [MyAuthentication,]

    def get(self,request,*args,**kwargs):
        print(request)
        print(request.user)
        self.dispatch
        ret  = {
            'code':1000,
            'msg':'xxx'
        }
        return HttpResponse(json.dumps(ret),status=201)

 

在項目的根目錄下的urls.py 中添加路由的代碼以下:api

from app01 import views


urlpatterns = [
    url(r'^dog/', views.DogView.as_view()),
]

 

 

若是想實現必須是登錄後才能進行請求的話,只需重寫父類中的  authenticate  (進行一些邏輯認證)和 authenticate_header   就能達到認證的效果瀏覽器

啓動項目,在瀏覽器中輸入app

http://127.0.0.1:8000/dog/?token=123

 

‘攜帶token返回的結果以下iview

{"code": 1000, "msg": "xxx"}

  

沒有攜帶token返回的結果以下ide

http://127.0.0.1:8000/dog

  

 

源碼分析

那麼上面的源碼內部又是如何是現代的呢函數

源碼流程圖源碼分析

 

 源碼入口先從  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進行封裝
        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進行了封裝   

request = self.initialize_request(request, *args, **kwargs)
self.request = request  

 

那麼它內部作了些什麼呢 ,追蹤  self.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
        )

 

 

 咱們能夠看到    authenticators=self.get_authenticators()  這個貌似和認證有關係,追蹤get_authenticators 代碼以下:

    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 其代碼以下所示:

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

 

 到這裏咱們發現它是經過讀取應用的配置文件,到這裏神祕的面紗已經開始解開了,加入咱們本身寫的類若是本身的內部有  authentication_classes  這個屬性,而且其也是一個可迭代的對象,那麼根據面向對象的屬性,就會執行咱們

本身的self.authentication_classes  ,而不是默認到配置文件中去找。

 

對request的總結:

上面對原生的request對象進行了一些封裝和得到了認證的實例話對象的列表

 

執行認證

繼續追蹤 dispatch  中的  self.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 的意思是執行認證的意思,讓咱們追蹤其內部的代碼以下:

    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方法
    @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

 

 讓咱們進行追蹤  self._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()

 

 

執行認證類的authenticate方法

   這裏分三種狀況

     1.若是authenticate方法拋出異常,self._not_authenticated()執行後在拋出異常-----就是認證沒經過

     2.有返回值,必須是元組:(request.user,request.auth)

     3.返回None,表示當前認證不處理,等下一個認證來處理

 

 

 

返回值就是例子中的:

token_obj.user-->>request.user
token_obj-->>request.auth

經過認證自定義的authenticated沒有返回值,就執行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()   #AnonymousUser匿名用戶
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()  #None
        else:
            self.auth = None

 

 

 由於authenticate方法咱們本身重寫了,因此當執行authenticate()的時候就是執行咱們本身寫的認證

父類中的authenticate方法

 

    def authenticate(self, request):
        return (self.force_user, self.force_token)

咱們本身寫的

 

    def authenticate(self,request):
        token = request._request.GET.get('token')
        # 獲取用戶名和密碼,去數據校驗
        if not token:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        return ("dog",None)

 

 

因此到這裏咱們就搞懂了咱們在作用戶認證的時候爲何須要重其authenticate的方法了

 

以上的是咱們本身定義的 authentication_classes   它會去咱們的類屬性中使用咱們本身的,只需在須要認證接口的中設置類屬性 authentication_classes = [ 認證的邏輯函數 ]

全局認證

假如咱們進行全局認證的話,能夠在配置文件中設置,經過繼承父類的方式來實現,簡單的說就是 類屬性 authentication_classes 本身不定義使用父類的,看如下的代碼:

 

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

 

在以上的代碼中咱們能夠看到authentication_classes = = api_settings.DEFAULT_AUTHENTICATION_CLASSES 咱們能夠追蹤看到代碼以下:

def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']
    if setting == 'REST_FRAMEWORK':
        api_settings.reload()


setting_changed.connect(reload_api_settings)

 

 

這是它會去配置文件中讀取    REST_FRAMEWORK ,這時後咱們能夠在配置文件中添加這個配置,裏面的鍵是 DEFAULT_PARSER_CLASSES 值是認證函數的路徑

REST_FRAMEWORK = {
    # 全局使用的認證類
    "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Authtication', ],
}

 

 咱們根據上述配置的路徑,在應用api創建如下文件  utils/auth.py  代碼以下:

class Authtication(BaseAuthentication):
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        # 在rest framework內部會將整個兩個字段賦值給request,以供後續操做使用
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        pass

 

這時後全部的接口,必須通過認證後才能訪問,可是有的接口是不須要認證的,好比登錄的時候這時,咱們能夠在登錄的接口中不走父類(apiview) 的認證使用咱們本身 的,只需定義定義類屬性authentication_classes = [ ] 便可

class AuthView(APIView):
    """
    用於用戶登陸認證
    """
    authentication_classes = []

    def post(self,request,*args,**kwargs):
        pass

 

關於匿名用戶登錄的設置

有些時候用戶是在沒有登錄的時候,訪問咱們的接口,這時候咱們稱之爲匿名的用戶,咱們能夠對其進行簡單的設置

    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()

    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

 

在上面的源碼中咱們能夠看到 匿名用戶默認的request.user=AnonymousUser, request.auth = None ,咱們來寫一個接口來簡單的認證如下

class UserInfoView(APIView):

    authentication_classes = []

    def get(self,request,*args,**kwargs):
        print(request.user)
        print(request.auth )
        return HttpResponse('用戶信息')

 

 

爲其  配置url 

 url(r'^api/v1/info/$', views.UserInfoView.as_view()),

 

在瀏覽器中輸入

http://127.0.0.1:8000/api/v1/info/ 

 

測試的結果以下:

 

 

 源碼中的user 和token 的設置以下:

        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()

 

 咱們能夠看到 對user 和 token 的設置,咱們能夠定義一個函數,因此咱們能夠這樣設置,

REST_FRAMEWORK = {
    # 全局使用的認證類
    "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication', ],
    # "UNAUTHENTICATED_USER":lambda :"匿名用戶",
    "UNAUTHENTICATED_USER": None
    "UNAUTHENTICATED_TOKEN":None,
}

 

 測試的結果以下:

 

 

 

drf的內置認證類

rest_framework裏面內置了一些認證,咱們本身寫的認證類最好都要繼承內置認證類 "BaseAuthentication"

from rest_framework.authentication import BasicAuthentication

BaseAuthentication源碼:

class BaseAuthentication(object):
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        #內置的認證類,authenticate方法,若是不本身寫,默認則拋出異常
        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.
        """
        #authenticate_header方法,做用是當認證失敗的時候,返回的響應頭
        pass

 

 

其它內置認證類

rest_framework裏面還內置了其它認證類,咱們主要用到的就是BaseAuthentication,剩下的不多用到

 

 

總結

 本身寫認證類方法梳理

 (1)建立認證類

  • 繼承BaseAuthentication    --->>1.重寫authenticate方法;2.authenticate_header方法直接寫pass就能夠(這個方法必須寫)

(2)authenticate()返回值(三種)

  • None ----->>>當前認證無論,等下一個認證來執行
  • raise exceptions.AuthenticationFailed('用戶認證失敗')       # from rest_framework import exceptions
  •  有返回值元祖形式:(元素1,元素2)      #元素1複製給request.user;  元素2複製給request.auth

 (3)局部使用

  • authentication_classes = [BaseAuthentication,]

(4)全局使用

 

#設置全局認證
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
}
相關文章
相關標籤/搜索