一、简介 |
Django REST Framework(简称DRF),是一个用于构建Web API的强大且灵活的工具包。
先说说REST:REST是一种Web API设计标准,是目前比较成熟的一套互联网应用程序的API设计理论。REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。
Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是”表现层状态转化”。如果一个架构符合REST原则,就称它为RESTful架构。所以简单来说,RESTful是一种Web API设计规范,根据产品需求需要定出一份方便前后端的规范,因此不是所有的标准要求都需要遵循。
学习RESTful API的资料:RESTful API 设计指南、理解RESTful架构
二、安装配置 |
安装需求:
- Python(2.7,3.2,3.3,3.4,3.5,3.6)
- Django(1.10,1.11,2.0 alpha)
可选安装包:
- coreapi(1.32.0+) – 支持模式生成。
- Markdown(2.1.0+) – Markdown支持可浏览的API。
- django-filter(1.0.1+) – 过滤支持。
- django-crispy-forms – 改进的HTML显示过滤。
- django-guardian(1.1.1+) – 支持对象级别的权限控制。
安装:
pip install djangorestframework pip install markdown # Markdown support for the browsable API. pip install django-filter # Filtering support
三、知识预备 |
在开始介绍Django REST Framework之前需要了解下django的路由系统以及csrf中间件。
1.csrf校验:
基于中间件的process_view方法实现对请求的csrf_token验证
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')
四、简单认证示例 |
场景:用户查看自己购买的订单,需登陆验证
以下是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认证类列表就会使用我们自己定义的认证类,下面从源码角度分析
五、认证过程源码剖析 |
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进行验证,并且需要返回元祖。
六、配置认证类 |
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
}
七、内置认证类 |
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认证
八、总结 |
1.自定义认证类:
继承BaseAuthentication,重写authenticate方法和authenticate_header(pass就可以),authenticate()方法需要有三种情况(返回元祖、出现异常、返回none)。
2.认证配置:
#全局认证 REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',] } #局部认证 authentication_classes = [BaseAuthentication,] #是某个视图不进行认证 authentication_classes =[]
3.源码流程: