Django Rest Framework源碼剖析(一)-----認證

1、簡介

Django REST Framework(簡稱DRF),是一個用於構建Web API的強大且靈活的工具包。html

先說說REST:REST是一種Web API設計標準,是目前比較成熟的一套互聯網應用程序的API設計理論。REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的。Fielding是一個很是重要的人,他是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的做者之1、Apache基金會的第一任主席。因此,他的這篇論文一經發表,就引發了關注,而且當即對互聯網開發產生了深遠的影響。python

Fielding將他對互聯網軟件的架構原則,定名爲REST,即Representational State Transfer的縮寫。我對這個詞組的翻譯是」表現層狀態轉化」。若是一個架構符合REST原則,就稱它爲RESTful架構。因此簡單來講,RESTful是一種Web API設計規範,根據產品需求須要定出一份方便先後端的規範,所以不是全部的標準要求都須要遵循。git

學習RESTful API的資料:RESTful API 設計指南理解RESTful架構github

2、安裝配置

安裝需求:django

  • Python(2.7,3.2,3.3,3.4,3.5,3.6)
  • Django(1.10,1.11,2.0 alpha)

可選安裝包:後端

安裝:api

  pip install djangorestframework
  pip install markdown       # Markdown support for the browsable API.
  pip install django-filter  # Filtering support

 

3、知識預備

在開始介紹Django REST Framework以前須要瞭解下django的路由系統以及csrf中間件瀏覽器

1.csrf校驗:服務器

基於中間件的process_view方法實現對請求的csrf_token驗證restful

2.不須要csrf驗證方法:

fbv:

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def index(request):
    pass

cbv:

方式一:

###方式一
from django.shortcuts import render,HttpResponse
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
from django.views import View

class Myview(View):
    @method_decorator(csrf_exempt)    #必須將裝飾器寫在dispatch上,單獨加不生效
    def dispatch(self, request, *args, **kwargs):
        return super(Myview,self).dispatch(request,*args,**kwargs)
    def get(self):
        return HttpResponse('get')

    def post(self):
        return HttpResponse('post')

    def put(self):
        return HttpResponse('put')

方式二:

from django.shortcuts import render,HttpResponse
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
from django.views import View


@method_decorator(csrf_exempt,name='dispatch')##name參數指定是dispatch方法
class Myview(View):
    
    def dispatch(self, request, *args, **kwargs):
        return super(Myview,self).dispatch(request,*args,**kwargs)
    def get(self):
        return HttpResponse('get')

    def post(self):
        return HttpResponse('post')

    def put(self):
        return HttpResponse('put')
4、簡單認證示例

場景:用戶查看本身購買的訂單,需登錄驗證

如下是demo:

models.py

from django.db import models

class UserInfo(models.Model):
    user_type_choice = (
        (1,"普通用戶"),
        (2,"會員"),
    )
    user_type = models.IntegerField(choices=user_type_choice)
    username = models.CharField(max_length=32,unique=True)
    password = models.CharField(max_length=64)


class UserToken(models.Model):
    user = models.OneToOneField(to=UserInfo)
    token = models.CharField(max_length=64)

認證url(urls.py)

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [

    url(r'^api/v1/auth', views.AuthView.as_view()),
    url(r'^api/v1/order', views.OrderView.as_view()),
]

views.py

from django.shortcuts import  HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.authentication import BaseAuthentication
from . import models
from rest_framework import exceptions
import hashlib
import time


class Authentication(BaseAuthentication):
    """
    認證類
    """

    def authenticate(self, request):
        token = request._request.GET.get("token")
        toke_obj = models.UserToken.objects.filter(token=token).first()
        if not toke_obj:
            raise exceptions.AuthenticationFailed("用戶認證失敗")
        return (toke_obj.user, toke_obj)  # 這裏返回值一次給request.user,request.auth

    def authenticate_header(self, val):
        pass


def md5(user):
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding="utf-8"))
    m.update(bytes(ctime,encoding="utf-8"))
    return m.hexdigest()

class AuthView(APIView):
    """登錄認證"""
    def dispatch(self, request, *args, **kwargs):
        return super(AuthView, self).dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return HttpResponse('get')

    def post(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': "登陸成功"}
        try:
            user = request._request.POST.get("username")
            pwd = request._request.POST.get("password")
            obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = "用戶名或密碼錯誤"
            else:
                token = md5(user)
                models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
                ret['token'] = token

        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = "請求異常"

        return JsonResponse(ret)



class OrderView(APIView):
    '''查看訂單'''

    authentication_classes = [Authentication,]    #添加認證
    def get(self,request,*args,**kwargs):
        #request.user
        #request.auth
        ret = {'code':1000,'msg':"你的訂單已經完成",'data':"買了一個mac"}
        return JsonResponse(ret,safe=True)

用戶使用token訪問,不帶token或token錯誤會認證錯誤。

http://127.0.0.1:8000/api/v1/order?token=63743076dfaefa632f6acb302cf90400

###返回結果
{"code": 1000, "msg": "\u4f60\u7684\u8ba2\u5355\u5df2\u7ecf\u5b8c\u6210", "data": "\u4e70\u4e86\u4e00\u4e2amac"}

對於以上demo可能會有疑問爲何添加了authentication_classes認證類列表就會使用咱們本身定義的認證類,下面從源碼角度分析

5、認證過程源碼剖析

1.先從請求提及,咱們都知道在django(CBV)中,客戶端的發來的請求會執行視圖類的as_view方法,而as_view方法中會執行dispacth方法,而後在根據請求的類型執行相應的方法(get、post等)。

2.在上面的示例中,使用django rest framework中的視圖類須要繼承APIView,請求到達視圖類會執行視圖類的as_view方法,而OrderView中沒有as_view()方法,因此執行APIView的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)   #執行父類as_view()方法
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)

3.從以上源碼中能夠看到APIView中as_view又執行了父類的as_view方法,在看看APIView的父類是View類,這剛好是django中的view視圖類,如下是View類中的as_view()的源碼:

    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

4.從上源碼能夠看出View類的as_view()方法執行流程:驗證請求方法--->返回view函數名稱(view函數會執行dispatch方法),一旦有請求進來執行view函數-->執行dispatch方法

5.當APIView的as_view方法執行了父類的as_view方法之後,請求進來會執行view方法,view方法中會執行dispatch方法,而Oderview沒有dispatch方法,因此執行父類(APIView)的dispatch方法,下面的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
        request = self.initialize_request(request, *args, **kwargs)  #對django原始的request進行封裝,返回Request對象(新的對象)。
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)  #這裏request參數實則是Request對象 # 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

6.從以上源碼分析,執行APIView的dispatch方法時候會執行self.initialize_request方法,會對django原始的request進行封裝。再看看initialize_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類,
            request,         #django原始的request對象,封裝到Request中變成self._request  
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),  #開始認證流程
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

7.self.initialize_request()源碼分析,實例化Request()類,封裝原始的request,authenticators(認證),執行self.get_authenticators(),到了這裏就開始django rest framework的認證流程,如下是self.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] #列表生成式返回認證類的對象列表

8.self.get_authenticators()源碼分析,採用列表生成式,循環self.authentication_classes,實例化其中的每個類,返回列表,不難發現authentication_classes屬性正式咱們在認證的時候用到認證類列表,這裏會自動尋找該屬性進行認證。假若咱們的視圖類沒有定義認證方法呢?,固然django rest framework 已經給咱們加了默認配置,若是咱們沒有定義會自動使用settings中的DEFAULT_AUTHENTICATION_CLASSES做爲默認(全局)下面是APIView類中的共有屬性:

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
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

 9.繼續分析APIView的dispatch方法,此時執行self.inital方法,並將封裝事後的request對象(Reuqest)做爲參數進行傳遞,下面是self.inital()方法源碼:

    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)

10.在self.inital方法中會執行self.perform_authentication方法,而self.perform_authentication方法用會執行request.user,此時的request是Request對象,因此需分析Request類中的user屬性,如下是Request部分類源碼:

class Request(object):
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers_classes(list/tuple). The parsers to use for parsing the
          request content.
        - authentication_classes(list/tuple). The authentications 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      #django原生的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

  ####
    @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() #下hi ing
        return self._user

11.從上源碼分析,在Request對象中,user屬性是一個屬性方法,並會執行self._authentication方法,在繼續看Request對象的self._authentication方法:

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:
            try:
          #執行認證類的authenticate方法
                #這裏分三種狀況
                #1.若是authenticate方法拋出異常,self._not_authenticated()執行
                #2.有返回值,必須是元組:(request.user,request.auth)
                #3.返回None,表示當前認證不處理,等下一個認證來處理
                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  #返回值對應示例中的token_obj.user和token_obj return

        self._not_authenticated()

12.從上源碼分析,Request對象的self._authentication中循環self.authenticators(該列表是由認證對象構成的[對象1,對象2]),並執行每個對象中的authenticate方法返回tuple,同時對該過程其進行了異常捕捉,有異常將返回給用戶,下面是異常驗證邏輯:

  • 若是有異常則執行self._not_authenticated()方法,繼續向上拋異常。

  • 若是有返回值必須是一個元組,分別賦值給self.user, self.auth(request.user和request.auth),並跳出循環。

  • 若是返回None,則由下一個循環處理,若是都爲None,則執行self._not_authenticated(),返回 (AnonymousUser,None)

13.當都沒有返回值,就執行self._not_authenticated(),至關於匿名用戶,沒有經過認證,而且此時django會返回默認的匿名用戶設置AnonymousUser,如須要單獨設置匿名用戶返回值,則編寫須要寫UNAUTHENTICATED_USER的返回值:

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

14.因此通過以上分析,咱們須要進行認證時候,須要在每個認證類中定義authenticate進行驗證,而且須要返回元祖。

 

6、配置認證類

1.認證全局配置文件

通過認證的源碼流程剖析,DRF的認證全局配置在api_setting中,如下是api_setings部分源碼:

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)


def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']
    if setting == 'REST_FRAMEWORK':  #項目中settings.py的key
        api_settings.reload()


setting_changed.connect(reload_api_settings)

其中引用了django,settings.py中的REST_FRAMEWORK做爲key做爲配置,因此全局配置示例:

#全局認證配置
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]   #其中寫認證的類的路徑,不要在views中,這裏我放在了utils目錄下auth.py中
}

2.局部使用

局部某個視圖不須要認證,則在視圖類中加入authentication_classes=[]

authentication_classes = []    #authentication_classes爲空,表明不須要認證

3.匿名用戶配置:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',],  #其中寫認證的類的路徑,不要在views中,這裏我放在了utils目錄下auth.py中
    "UNAUTHENTICATED_USER": lambda:"匿名"#匿名用戶配置,只須要函數或類的對應的返回值,對應request.user="匿名"
"UNAUTHENTICATED_token": None,#匿名token,只須要函數或類的對應的返回值,對應request.auth=None

}
7、內置認證類

1.BaseAuthentication

BaseAuthentication是django rest framework爲咱們提供了最基本的認證類,正如源碼流程同樣,該類中其中定義的兩個方法authenticate和authenticate_header(認證失敗返回的響應頭),使用時候重寫該兩個方法進行認證,正如示例:

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

2.其餘認證類

##路徑:rest_framework.authentication

BasicAuthentication  #基於瀏覽器進行認證
SessionAuthentication #基於django的session進行認證
RemoteUserAuthentication #基於django admin中的用戶進行認證,這也是官網的示例
TokenAuthentication #基於drf內部的token認證

 

8、總結

1.自定義認證類:

繼承BaseAuthentication,重寫authenticate方法和authenticate_header(pass就能夠),authenticate()方法須要有三種狀況(返回元祖、出現異常、返回none)。

2.認證配置:

#全局認證
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
}

#局部認證
authentication_classes = [BaseAuthentication,]

#是某個視圖不進行認證
authentication_classes =[]

3.源碼流程:

相關文章
相關標籤/搜索