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
可选安装包:后端
安装: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.源码流程: