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.源码流程:

相关文章
相关标签/搜索