drf 許可權校驗設定與原始碼分析

雲崖先生發表於2020-11-01

許可權校驗

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

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

準備工作

模型表

   下面是模型表,對於不同等級的使用者,訪問同一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()方法接受兩個引數,分別是requestview,也就是檢視類的例項化本身。

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到請求頭中

結果演示

   再次使用jacktoken進行登入,可以發現他只會看到免費電影了。

   image-20201101164843974

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

   image-20201101165031651

   倘若使用yunyatoken進行登入,則可以檢視到所有的電影。

   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)
                )

相關文章