drf 許可權校驗設置與源碼分析

許可權校驗

   許可權校驗和認證校驗必須同時使用,並且許可權校驗是排在認證校驗之後的,這在源碼中可以查找到其執行順序。

   許可權校驗也很重要,認證校驗可以確保一個用戶登錄之後才能對介面做操作,而許可權校驗可以依據這個登錄用戶的類型來限定能對介面做那些操作。

準備工作

模型表

   下面是模型表,對於不同等級的用戶,訪問同一url,能夠獲取到的電影內容也不一樣。

from django.db import models


# Create your models here.

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_type = models.IntegerField(choices=(
        [0,"普通用戶"],
        [1,"黃金VIP"],
        [2,"鑽石VIP"],
    ))
    user_token = models.CharField(max_length=64, null=True, unique=True)

class Film(models.Model):
    film_id = models.AutoField(primary_key=True)
    film_name = models.CharField(max_length=32)
    film_grade = models.IntegerField(
        choices=(
            [0,"免費電影"],
            [1,"黃金VIP專享"],
            [2,"鑽石VIP專享"]
        )
    )

   用戶表數據如下:

   image-20201101155300389

   電影表數據如下:

   image-20201101155452968

序列類

   採用模型類序列器,並且只對電影做序列化:

from rest_framework import serializers
from app01 import models

class FilmModelSerializers(serializers.ModelSerializer):
    class Meta:
        model = models.Film
        fields = "__all__"

認證校驗

   下面是認證許可權,只有登錄後的用戶才可以看到電影資訊。如果沒有登錄將不允許查看所有電影的頁面,或者你也可以不設置認證校驗,但是在許可權設置中可以設置匿名用戶直接返回一個False也行。

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import APIException
from app01 import models

class LoginAuth(BaseAuthentication):
    def authenticate(self,request):
        token = request.META.get("HTTP_TOKEN")
        if not token:
            # 未登錄
            raise APIException("未登錄,無法查看本電影網站,請先登錄")
        user_obj = models.User.objects.filter(user_token=token).first()
        if not user_obj:
            raise APIException("token無效,登錄失敗")
        else:
            return user_obj,user_obj.user_type  # 將用戶對象本身以及用戶的類型存儲到request.user以及request.auth中

視圖

   下面是視圖的程式碼:

from uuid import uuid4

from rest_framework.generics import ListAPIView
from rest_framework.generics import GenericAPIView
from rest_framework.views import APIView
from rest_framework.views import Response

from app01 import models
from app01 import serializationClass
from app01 import app01_auth


class FilmAPI(ListAPIView,GenericAPIView):
    authentication_classes = [app01_auth.LoginAuth]  # 必須登錄
    queryset = models.Film.objects.all()  # 默認查看所有,任何用戶都是
    serializer_class = serializationClass.FilmModelSerializers
    
    def get(self,request):
        return self.list(request)


class Login(APIView):
    def post(self,request):
        user_dict = {
            "user_name":request.data.get("user_name"),
            "user_password":request.data.get("user_password"),
        }
        user_obj = models.User.objects.filter(**user_dict).first()
        if not user_obj:
            return Response(data="用戶名或密碼錯誤")
        else:
            token = uuid4()  # 創建token
            user_obj.user_token = token
            user_obj.save()
            return Response(data="登錄成功",headers={"token":token})  # 返回token到請求頭中

url

   下面是路由。

from django.contrib import admin
from django.urls import path

from app01 import views


urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/',views.Login.as_view()),
    path('api/film/',views.FilmAPI.as_view()),
]

簡單嘗試

   現在我們啟動django項目試試。可以發現,當我們在請求頭中設置好token並朝api發送GET請求時,它會返回所有的數據。

   我們登錄的是jack這個用戶,他應該只能看到免費電影,而類似泰坦尼克號這種電影是不應該讓他看見的。

   image-20201101162251081

自訂製許可權

   寫一個類,繼承BasePermission,重寫has_permission()方法,如果許可權通過,就返回True,不通過就返回False。

許可權校驗

   接下來我們來寫許可權校驗,has_permission()方法接受兩個參數,分別是request和view,也就是視圖類的實例化本身。

from rest_framework.permissions import BasePermission
from app01 import models
from django.db.models import Q

class UserPermission(BasePermission):
    def has_permission(self, request, view):
        if request.auth == 1:  # 如果是普通用戶,修改當前獲取的資源為免費電影
            view.queryset = models.Film.objects.filter(film_grade=1)
        elif request.auth == 2:  # 如果是黃金VIP,則只能獲取黃金VIP電影和免費電影
            view.queryset = models.Film.objects.filter(Q(film_grade=2) | Q(film_grade=1))
        else:
            pass  # 默認就是獲取所有,所以不用修改

        return True  # 許可權校驗完成,設置好了。普通用戶只能看免費電影

局部使用

   下面是局部使用,只需要用一個變數名為permission_classes的列表,將許可權校驗的類放入即可:

class FilmAPI(ListAPIView,GenericAPIView):
    authentication_classes = [app01_auth.LoginAuth]  # 必須登錄
    permission_classes = [app01_permissions.UserPermission]  # 做許可權設置

    queryset = models.Film.objects.all()  # 默認查看所有,任何用戶都是
    serializer_class = serializationClass.FilmModelSerializers

    def get(self,request):
        return self.list(request)

全局使用

   如果是全局使用,則需要到項目全局文件夾下的settings.py中進行設置:

# 全局使用
REST_FRAMEWORK={
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.app01_permissions.UserPermission',
    ],
}

   如果想取消某個介面的許可權認證設置,則在其中設置類屬性permission_classes是一個空列表。

   如下所示,登錄功能不需要驗證許可權,我們對他取消掉即可。

class Login(APIView):
	permission_classes = []  # 取消許可權驗證設置
	
    def post(self,request):
        user_dict = {
            "user_name":request.data.get("user_name"),
            "user_password":request.data.get("user_password"),
        }
        user_obj = models.User.objects.filter(**user_dict).first()
        if not user_obj:
            return Response(data="用戶名或密碼錯誤")
        else:
            token = uuid4()  # 創建token
            user_obj.user_token = token
            user_obj.save()
            return Response(data="登錄成功",headers={"token":token})  # 返回token到請求頭中

結果演示

   再次使用jack的token進行登錄,可以發現他只會看到免費電影了。

   image-20201101164843974

   而使用ken的token進行登錄,他將看不到鑽石VIP的電影,如泰坦尼克號:

   image-20201101165031651

   倘若使用yunya的token進行登錄,則可以查看到所有的電影。

   image-20201101164937760

內置許可權

   如果你是使用auth組件來做的一系列登錄等,則可以使用內置許可權。

   它會判斷該用戶的is_staff欄位是否為True。

   內置許可權的類使用有很多,如下所示:

   image-20201101164309110

基本演示

# 演示一下內置許可權的使用:IsAdminUser,控制是否對網站後台有許可權的人
# 1 創建超級管理員
# 2 寫一個測試視圖類

from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication
class TestView3(APIView):
    authentication_classes=[SessionAuthentication,]  # 必須有認證
    permission_classes = [IsAdminUser]  # 許可權設置
    def get(self,request,*args,**kwargs):
        return Response('這是22222222測試數據,超級管理員可以看')
        
# 3 超級用戶登錄到admin,再訪問test3就有許可權
# 4 正常的話,普通管理員,沒有許可權看(判斷的是is_staff欄位)

源碼分析

   許可權校驗排在認證校驗之後,這在源碼中可以查看到。

   它的源碼相比於認證的源碼來說簡單的多,認證的執行是在request.user中進行的,而它直接是在當前視圖類中進行,所以很簡單。

# APIView---->dispatch---->initial--->self.check_permissions(request)(APIView的對象方法)
    def check_permissions(self, request):
        # 遍歷許可權對象列表得到一個個許可權對象(許可權器),進行許可權認證 遍歷  for (許可權認證的實例化對象)
        for permission in self.get_permissions():
            # 許可權類一定有一個has_permission許可權方法,用來做許可權認證的
            # 參數:許可權對象self、請求對象request、視圖類對象
            # 返回值:有許可權返回True,無許可權返回False
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )
Tags: