drf 認證校驗及源碼分析

認證校驗

   認證校驗是十分重要的,如用戶若是不登錄就不能訪問某些接口。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

   image-20201031170412050

   商品表的數據以下:code

   image-20201031171236819

   如今,只有當用戶登陸後,纔可以訪問商品的接口。

   也就是說,用戶的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="登陸失敗,用戶名或密碼錯誤")

url

   使用自動生成路由:

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

   若是你沒有配置認證類,其實它會走默認的認證類。

   老規矩,關於配置認證類時依舊是先用局部的,再用全局的,最後是用默認的,若是你的上面的源碼確實有感受了的話,應該可以看懂。

相關文章
相關標籤/搜索