認證校驗是十分重要的,如用戶若是不登錄就不能訪問某些接口。django
drf
中認證的寫法流程以下:api
1.寫一個類,繼承BaseAuthentication,而且覆寫其authenticate方法app
2.當認證經過後應該返回兩個值,而且第一個值會傳遞給request.user這個屬性中,第二個值將會傳遞給request.auth這個屬性中源碼分析
3.若是認證失敗,則拋出異常APIException或者AuthenticationFailed,它會自動捕獲並返回post
4.當前認證類設置是全局使用仍是局部使用ui
咱們有一個登陸功能,而且還有一個查詢商品的接口,只有當用戶登陸後才能進行查詢,不然就不能夠。url
兩張表以下:spa
from django.db import models class User(models.Model): # 用戶 user_id = models.AutoField(primary_key=True) user_name = models.CharField(max_length=32) user_password = models.CharField(max_length=32) user_token = models.CharField(max_length=64,unique=True,null=True) # token,惟一 def __str__(self): return self.user_name class Meta: db_table = "" managed = True verbose_name = "User" verbose_name_plural = "Users" class Merchandise(models.Model): # 商品 merchandise_id = models.AutoField(primary_key=True) merchandise_name = models.CharField(max_length=32) merchandise_price = models.IntegerField() def __str__(self): return self.merchandise_name class Meta: db_table = "" managed = True verbose_name = "Merchandise" verbose_name_plural = "Merchandises"
用戶表的數據以下:rest
商品表的數據以下:code
如今,只有當用戶登陸後,纔可以訪問商品的接口。
也就是說,用戶的token
自動若是爲空,將會被認爲沒有登錄。
下面是序列類,咱們只展現商品,用戶列表將不會展現:
from rest_framework.serializers import ModelSerializer from . import models class MerchandiseModelSerializer(ModelSerializer): class Meta: model = models.Merchandise fields = "__all__"
視圖,咱們只寫了關於用戶登陸與商品的接口:
from uuid import uuid4 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from . import models from . import ser from . import authLogin # 導入認證的文件 class MerchandiseAPI(ModelViewSet): queryset = models.Merchandise.objects.all() serializer_class = ser.MerchandiseModelSerializer class Login(APIView): def post(self,request): # 表明用戶登陸 login_msg = { "user_name": request.data.get("user_name"), "user_password": request.data.get("user_password"), } user_obj = models.User.objects.filter(**login_msg).first() if user_obj: token = uuid4() # 生成隨機字符串 user_obj.user_token = token user_obj.save() return Response(data="登陸成功",headers={"token":token}) # 返回隨機字符串 else: return Response(data="登陸失敗,用戶名或密碼錯誤")
使用自動生成路由:
from django.contrib import admin from django.urls import path, re_path from rest_framework.routers import SimpleRouter from app01 import views router = SimpleRouter() router.register("merchandises",views.MerchandiseAPI) urlpatterns = [ path('admin/', admin.site.urls), path('login/',views.Login.as_view()), ] urlpatterns.extend(router.urls)
接下來咱們要書寫一個認證類:
from rest_framework.authentication import BaseAuthentication # 繼承的基類 from rest_framework.exceptions import AuthenticationFailed # 異常 from . import models from django.http import request class LoginVerify(BaseAuthentication): def authenticate(self, request): token = request.META.get("HTTP_TOKEN") # 若是在請求頭中設置的是token的key名,獲取時必定要全大寫並加上HTTP if not token: raise AuthenticationFailed("請求失敗,請求頭中缺乏token") else: user_obj = models.User.objects.filter(user_token=token).first() # 獲取用戶對象 if user_obj: return user_obj,user_obj.user_token # 返回用戶自己和token。這樣request.user裏面就能拿到該用戶了 else: raise AuthenticationFailed("token不存在,用戶不存在,請不要僞造登陸")
只須要在商品接口中設置一個類屬性,該接口便會進行認證。
class MerchandiseAPI(ModelViewSet): authentication_classes = [authLogin.LoginVerify] # 使用認證 queryset = models.Merchandise.objects.all() serializer_class = ser.MerchandiseModelSerializer
只須要在settings.py
中進行配置。
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.authLogin.LoginVerify",] }
若是想取消某個接口的認證,則在其中設置類屬性authentication_classes
是一個空列表。
以下所示,登陸功能不須要驗證,咱們對他取消掉便可。
class Login(APIView): authentication_classes = []
因爲modelViewSet
繼承自APIView
,因此咱們直接看as_view()
,在下面這一句代碼中,將會對request
進行二次封裝。
def dispatch(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs request = self.initialize_request(request, *args, **kwargs) # 這裏 self.request = request self.headers = self.default_response_headers # deprecate?
在二次封裝中,實例化出了一個Request
對象並返回了,在實例化時,會調用self.get_authenticators()
方法,此時的self
是咱們自定義的視圖類,切記這一點。
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), # 看這裏,獲取認方式 negotiator=self.get_content_negotiator(), parser_context=parser_context )
下面是get_authenticators()
的代碼,能夠看見它會循環self.authentication_classes
這個可迭代對象,若是你沒有傳遞這個可迭代對象,那麼該對象是一個默認的設置。
def get_authenticators(self): return [auth() for auth in self.authentication_classes] # ( authLogin.LoginVerify調用,實例化 )
若是沒有傳遞,將會找到APIView
中的默認設置:
class APIView(View): renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # 默認的設置,默認的認證類,能夠本身看一下
若是有進行傳遞,能夠發現它是使用了一個括號,這就表明會調用,因爲傳入的是一個類,因此它會進行實例化。
因此咱們能夠認爲request.authenticators
這個參數是一個tuple
,裏面包含了認證類的實例化對象。
而後,request
就被二次包裝完畢了。接下來執行 self.initial()
,如今的self
依然是咱們自定義的視圖類。
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) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs)
下面是self.inital()
的代碼,
def initial(self, request, *args, **kwargs): self.format_kwarg = self.get_format_suffix(**kwargs) self.perform_authentication(request) # 只看這個,認證相關的 self.check_permissions(request) self.check_throttles(request)
到了self.perform_authentication()
時,它傳遞進了個request
,而且會去找user
這個屬性抑或是被property
裝飾的方法,因此咱們須要到Request
這個類中去找,須要注意的是若是user
是一個方法,這表明會自動傳遞進self
,此時的self
則是咱們通過二次封裝的request
對象。
能夠發現它是一個被裝飾的方法。很顯然咱們沒有_user
這個方法或屬性,會執行with
語句,其實直接看self._authenticate()
便可。再次強調,這次的self
是二次封裝的request
對象。
@property def user(self): if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() return self._user
下面是整個代碼的核心。
def _authenticate(self): for authenticator in self.authenticators: # 循環認證類對象 ( authLogin.LoginVerify的實例化 ) try: user_auth_tuple = authenticator.authenticate(self) # 這裏會找authenticate方法並將request對象進行傳遞,咱們的認證類繼承了BaseAuthentication這個類,它會實現一個接口方法, 但會拋出異常。 except exceptions.APIException: # 若是沒有實現接口方法,或在驗證時拋出異常都會被這裏捕獲 self._not_authenticated() # 執行這裏 self.user將會是匿名用戶AnonymousUser,而self.auth則是None raise if user_auth_tuple is not None: # 若是返回的值不是空 self._authenticator = authenticator self.user, self.auth = user_auth_tuple # 分別賦值給self.user,以及self.auth中 return # 返回 self._not_authenticated() # 上面有認證對象就會return,沒有仍是設置匿名用戶和None
其實看了源碼後,你能夠發現咱們的認證類能夠不繼承BaseAuthentication
,可是推薦繼承會更規範,由於這個基類實現了抽象接口。
其次,它將返回的兩個值分別賦值給了request.user
以及request.auth
。
若是你沒有返回值,那麼對應的,request.user
就是匿名用戶,request.auth
就是None
。
若是你沒有配置認證類,其實它會走默認的認證類。
老規矩,關於配置認證類時依舊是先用局部的,再用全局的,最後是用默認的,若是你的上面的源碼確實有感受了的話,應該可以看懂。