在聊APIView以前, 咱們先重溫一下django2.x的CBV流程python
a. 對於django而言, 當瀏覽器請求到達以後,按照規則首先會通過各大中間件(Middleware)(process_request, process_view, process_response), 而與CBV掛鉤的即是各中間件的process_view方法,以CSRFViewMiddleware爲例,django會默認開啓全局CSRF驗證,而在實際開發中,一部分視圖處理類是不須要CSRF驗證的,因此咱們須要在處理類方法上加上@method_decorator(csrf_exempt)裝飾器來取消驗證,下面是CSRFViewMiddleware類下的process_view的部分源碼,django
def process_view(self, request, callback, callback_args, callback_kwargs): if getattr(request, 'csrf_processing_done', False): return None # Wait until request.META["CSRF_COOKIE"] has been manipulated before # bailing out, so that get_token still works if getattr(callback, 'csrf_exempt', False): return None
b.爲何執行了視圖類的as_view(), 便可以處理各類類型(GET, POST, DELETE, HEAD等等)請求呢? 咱們知道,視圖類是利用dispatch(self, *args, **kwargs)的反射來實現請求(GET/POST......)與處理方法( get(self, request) / post(self, request) )之間的映射,因此實際上as_view()方法是調用了視圖類中的dispatch來實現調用具體的請求處理方法的。下面是as_view()的部分源碼:後端
1 def as_view(cls, **initkwargs): 2 """Main entry point for a request-response process.""" 3 for key in initkwargs: 4 if key in cls.http_method_names: 5 raise TypeError("You tried to pass in the %s method name as a " 6 "keyword argument to %s(). Don't do that." 7 % (key, cls.__name__)) 8 if not hasattr(cls, key): 9 raise TypeError("%s() received an invalid keyword %r. as_view " 10 "only accepts arguments that are already " 11 "attributes of the class." % (cls.__name__, key)) 12 13 def view(request, *args, **kwargs): 14 self = cls(**initkwargs) 15 if hasattr(self, 'get') and not hasattr(self, 'head'): 16 self.head = self.get 17 self.request = request 18 self.args = args 19 self.kwargs = kwargs 20 return self.dispatch(request, *args, **kwargs) 21 view.view_class = cls 22 view.view_initkwargs = initkwargs 23 24 # take name and docstring from class 25 update_wrapper(view, cls, updated=()) 26 27 # and possible attributes set by decorators 28 # like csrf_exempt from dispatch 29 update_wrapper(view, cls.dispatch, assigned=()) 30 return view
瞭解了CBV, 那APIView又是怎麼一回事?api
a. APIView是View的子類,其處理請求流程與View一致,但APiView內部對原生request對象進行了封裝,下面是其封裝request的部分源碼瀏覽器
能夠看出原生request對象被賦到新建request對象的_request屬性中,接下來即是基於APIView的認證了,它又是如何實現的呢?restful
在先後端分離的項目中, 經常須要判斷用戶是否登錄, 其登陸狀態的維持須要藉助於令牌(Token),若是在不整合restframework框架的狀況下,咱們要實現這一功能能夠自定義一個校驗裝飾器來裝飾每個須要登錄以後才能執行的試圖函數或者視圖類方法,而在restframework中,它變得更加靈活與規範, 具體體如今APIView提供支持多重驗證,驗證與視圖函數較低的耦合度,支持全局配置驗證規則等等,下面咱們從APIView的源碼中詳細的瞭解這一過程:app
1. 從第一部分對apiview的分析中, 咱們能夠知道, APIView新構建了一個request,該request對象不只封裝了原生request對象的全部屬性(_request),並且擴展了不少針對驗證,訪問頻率限制,權限控制等等新的特性;框架
2. 就認證(authentication)而言,前後端分離
那麼子類(基類爲APIView)的authentication_classes屬性爲自定義的驗證規則,那他又是如何執行這一規則的呢? 從第一部分的APIView建立新的request對象中咱們知道,其request對象的authenticators屬性爲get_authenticators()方法的返回值,那麼進入get_authenticators():iview
怎麼定義一個處理用戶認證的類呢?答案位於rest_framework.authentication下的BaseAuthentication。 須要說明的是,繼承該類的認證類必須擁有其authenticate(self, request, )以及authenticate_header(self, request)方法
歐克,到這裏的話, 咱們來實戰一下
咱們在名爲testrestful中建立兩張表,一張存用戶基本信息,一張存用戶token, 並在項目根目錄下執行python manage.py makemigrations、python manage.py migrate命令,並手動添加一些用戶數據到UserProfile
# models.py (testrestful)
from django.db import models from django.contrib.auth.models import User # Create your models here. class UserProfile(models.Model): # user = models.OneToOneField(to='User', on_delete=models.CASCADE) username = models.CharField(max_length=32, unique=True, null=False, blank=False) password = models.CharField(max_length=32, unique=False, null=False, blank=False) level_choice = ( (1, '普通用戶'), (2, 'VIP'), (3, 'SVIP'), ) level = models.SmallIntegerField(choices=level_choice, null=False, blank=False) def __str__(self): return '[model: %s]' % self.username class UserToken(models.Model): user = models.OneToOneField(to='UserProfile',on_delete=models.CASCADE) token = models.CharField(max_length=64) expired = models.DateTimeField(null=True)
在testrestful目錄下新建utils包,包下新建auth.py(也能夠直接在views.py中建立, )
其流程是: 已註冊的用戶發起登錄請求 ----> 認證處理 (用戶名及密碼正確則派發新的Token) ------> 用戶保存token 後續請求附上token獲得相應數據
from rest_framework import exceptions from rest_framework.authentication import BasicAuthentication, BaseAuthentication from testrestful import models import hashlib import uuid def md5Token(user: models.UserProfile)->str: """用於生成token""" # 用戶名惟一, 並將其做爲md5對象的salt hash = hashlib.md5(user.username.encode('utf-8')) cur_data = bytes(str(uuid.uuid1()), encoding='utf-8') hash.update(cur_data) return hash.hexdigest() class UserAuthentication(BaseAuthentication): """用於認證token""" def authenticate(self, request): token = request._request.GET.get('token', None) u_token_obj = models.UserToken.objects.get(token=token) if not u_token_obj: raise exceptions.AuthenticationFailed('用戶身份驗證失敗!') else: # resframework 會將這兩個元組返回給request, 以便後續操做 return (u_token_obj.user, u_token_obj) def authenticate_header(self, request): """當認證失敗的時候, 返回給瀏覽器的響應頭""" pass
若是用戶經過認證, 則以(key1, key2) 做爲執行返回值,並裝載到request對象中,key1對應request.user,key2對應request.auth
那麼對應的視圖類就很好辦了,只須要添加驗證規則類就歐克
from django.http.response import JsonResponse from rest_framework.response import Response from rest_framework.views import APIView from testrestful import models from testrestful.utils.auth import UserAuthentication, md5Token class Login(APIView): """用於接收登陸請求,派發或更新token""" # 已經在配置中配置了全局認證類, 而匿名用戶不須要認證 authentication_classes = [] msg = dict() def post(self, request): # 此時用戶沒有登陸,則返回默認匿名用戶, 這一用戶一樣可以實現自定義 print('login_user:\t', request.user) username = request._request.GET.get('username', None) password = request._request.GET.get('password', None) try: user = models.UserProfile.objects.filter(username=username, password=password).first() # 派發token token = md5Token(user) # token存在則更新, 不存在則建立 models.UserToken.objects.update_or_create(user=user, defaults={'token': token}) self.msg['flag'] = 1 self.msg['content'] = token print(self.msg) except Exception as e: print(e) self.msg['flag'] = 0 self.msg['content'] = "用戶名或密碼錯誤" return JsonResponse(self.msg) # Create your views here. class UserInfo(APIView): """用於獲取用戶信息""" authentication_classes = [UserAuthentication,] def dispatch(self, request, *args, **kwargs): return super(UserInfo, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): # 在UserAuthentication中的authenticate方法返回的元組被添加到了request對象中,元組第一個對象就是request.user, 第二個就是request.auth print(request.user) print(request.auth) return Response("ok") def post(self, request): return Response("POST Response") def delete(self, request): return Response("DELETE Response")
下面以postman作測試:
a. 登錄
b. 獲取用戶信息(需登錄)
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ['testrestful.utils.auth.UserAuthentication', ], 'UNAUTHENTICATED_USER': lambda : '匿名用戶(request.user)', 'UNAUTHENTICATED_TOKEN': lambda : '匿名用戶(request.auth)', }
配置完成以後, 與CSRF類似, 須要用戶認證的試圖類就不須要authentication_classes這一行了,不須要用戶認證的匿名用戶視圖類覆蓋authentication_class就歐克
拿本例舉例: