1. 程式人生 > 實用技巧 >drf-認證許可權頻率

drf-認證許可權頻率

一 認證Authentication

1.1 自定義認證方案

認證的實現

1 寫一個類,繼承BaseAuthentication,重寫authenticate,認證的邏輯寫在裡面,認證通過,返回兩個值,一個值最終給了Requet物件的user,認證失敗,拋異常:APIException或者AuthenticationFailed

2 全域性使用,區域性使用

案例:

登陸介面,查詢圖書介面,必須登入後才能檢視,token資訊放在頭裡(認證元件),全域性使用,區域性禁用(login禁用)

使用simplerouter自動生成路由

1.1.1 編寫models

class User(models.Model):
    username
=models.CharField(max_length=32) password=models.CharField(max_length=32) user_type=models.IntegerField(choices=((1,'超級使用者'),(2,'普通使用者'),(3,'二筆使用者'))) class UserToken(models.Model): user=models.OneToOneField(to='User') token=models.CharField(max_length=64)

1.1.2 新建認證類

在app下新建一個py檔案

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01.models import UserToken


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        if token:
            user_token 
= UserToken.objects.filter(token=token).first() if user_token: return user_token.user, token else: raise AuthenticationFailed('認證失敗') else: raise AuthenticationFailed('請求頭中需要攜帶token')

1.1.3 編寫檢視

from rest_framework.decorators import action
from app01 import models
from rest_framework.views import APIView
import uuid
from app01.ser import BookSerializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
class Login(APIView):
    authentication_classes = []#意思是區域性禁用
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = models.users.objects.filter(username=username, password=password).first()
        if user:
            token = uuid.uuid4()
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            return Response({'status': 100, 'msg': '登陸成功', 'token': token})
        else:
            return Response({'status': 101, 'msg': '登陸失敗'})


# 使用ModelViewSet編寫5個介面
class Bookview(ModelViewSet):
    queryset = models.book.objects.all()
    serializer_class = BookSerializers

    @action(methods=['GET', 'POST'], detail=True)
    def get_10(self,request,pk):
        book = self.get_queryset()[:10]  # 從0開始擷取一條
        ser = self.get_serializer(book, many=True)
        return Response(ser.data)

1.1.4 編寫序列化器

from rest_framework import serializers
from app01.models import book


class BookSerializers(serializers.ModelSerializer):
    class Meta:
        model = book
        fields = '__all__'

1.1.5 全域性使用

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",]
}

1.1.6 區域性使用

#區域性使用,只需要在檢視類里加入:
authentication_classes = [MyAuthentication]

1.2 認證的原始碼分析

在APIView下的dispath方法中有request = self.initialize_request(request, *args, **kwargs),這句程式碼重寫了request,並在request中加入了authenticators=self.get_authenticators()。讓我們來分析一下這段程式碼:

def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]

get_authenticators()只有簡單的那麼一句程式碼,這句程式碼是一個標準列表生成式,他的意思是遍歷self.authentication_classes加括號執行,並存放到列表當中。那麼self.authentication_classes裡面又有什麼東西呢?

點進去我們看見是authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES,他的意思是從配置檔案 中找到取出值賦值給authentication_classes這個列表,而這個列表中的值就是認證方法。預設的認證方法是無效的,因此我們要自己定義個authentication_classes並在裡面寫入自己定義的認證方法。

在drf中取資料的順序是先從自己定義的類中查詢,找不到再去專案下的settings.py檔案中查詢,最後再去框架中的配置檔案中查詢。知道了這個後就能很清楚的理解為何要自己定義authentication_classes和全域性使用,區域性使用,區域性禁用,全域性禁用的原理了。舉個例子:上面views.py的程式碼中我寫了authentication_classes = [],根據類方法的自呼叫[auth() for auth in self.authentication_classes]中的self.authentication_classes指的就是我自己寫的空列表,列表為空,生成式也為空,那麼到認證程式碼也是空,這就達到了區域性禁用的效果。(個人覺得這裡最繞的還是類的自呼叫啥的,所以還是再去好好學下類吧….)

迴歸主題知道了authentication_classes中放的是你寫的認證類之後,那[auth() for auth in self.authentication_classes]這程式碼的執行結果應該是認證類的物件列表,遞推回去那麼authenticators=self.get_authenticators()中authenticators就是認證類的物件列表,經過繞來繞去最後到了request物件下的_authenticate中。

-request.py下的   
def _authenticate(self):
        # 遍歷拿到一個個認證器,進行認證
        #self.authenticators 你在檢視類中配置的一個個的認證類:authentication_classes=[認證類1,認證類2],物件的列表
        for authenticator in self.authenticators:
            try:
                # 認證器(物件)呼叫認證方法authenticate(認證類物件self, request請求物件)
                # 返回值:登陸的使用者與認證的資訊組成的 tuple
                # 該方法被try包裹,代表該方法會拋異常,拋異常就代表認證失敗
                user_auth_tuple = authenticator.authenticate(self) #注意這self是request物件
            except exceptions.APIException:
                self._not_authenticated()
                raise

            # 返回值的處理
            if user_auth_tuple is not None:
                self._authenticator = authenticator
                # 如何有返回值,就將 登陸使用者 與 登陸認證 分別儲存到 request.user、request.auth
                self.user, self.auth = user_auth_tuple
                return
        # 如果返回值user_auth_tuple為空,代表認證通過,但是沒有 登陸使用者 與 登陸認證資訊,代表遊客
        self._not_authenticated()

上面這段程式碼就是核心程式碼了

二 許可權Permissions

許可權控制可以限制使用者對於檢視的訪問和對於具體資料物件的訪問。

  • 在執行檢視的dispatch()方法前,會先進行檢視訪問許可權的判斷
  • 在通過get_object()獲取具體物件時,會進行模型物件訪問許可權的判斷

2.1 自定義許可權

2.1.1 編寫許可權類

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

class MyPermission(BasePermission):
    def has_permission(self, request, view):
        # 不是超級使用者,不能訪問
        # 由於認證已經過了,request內就有user物件了,當前登入使用者
        user = request.user
        # 如果該欄位用了choice,通過get_欄位名_display()就能取出choice後面的中文
        # print(user.get_user_type_display())
        if user.user_type==1:
            return True
        else:
            return False

2.1.2 全域性使用

REST_FRAMEWORK={
     # 認證類的全域性使用
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",],
     # 許可權類的全域性使用
    'DEFAULT_PRESSION_CLASSES':['app01.app_auth.MyPermission,']
}

2.1.3 區域性使用和禁用

# 區域性使用只需要在檢視類里加入:
permission_classes = [MyPermission,]
# 區域性禁用只需要在檢視類里加入:
permission_classes = []

2.1.4 說明

如需自定義許可權,需繼承rest_framework.permissions.BasePermission父類,並實現以下兩個任何一個方法或全部

has_permission(self, request, view)是否可以訪問檢視, view表示當前檢視物件

has_object_permission(self, request, view, obj)是否可以訪問資料物件, view表示當前檢視, obj為資料物件

2.2 許可權原始碼分析

APIView—->dispatch—->initial—>self.check_permissions(request)(APIView的物件方法)通過上述的一層層查詢我們能找到許可權的核心原始碼

 def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )

許可權的核心程式碼很多地方其實和認證的差不多。self.get_permissions()點進去後會發現他的原始碼和認證的幾乎一樣,這就代表著他存放的是許可權類的物件集合成的列表。

遍歷一個個許可權列表得到一個個許可權物件(許可權器),進行許可權認證。

許可權類一定有一個has_permission許可權方法,用來做許可權認證的。

引數:許可權物件self、請求物件request、檢視類物件

返回值:有許可權返回True,無許可權返回False 如果返回值不是True就代表他沒許可權進行限制訪問

2.3 內建許可權(瞭解)

演示一下內建許可權的使用: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欄位)

三 頻率(限流)

可以對介面訪問的頻次進行限制,以減輕伺服器壓力。

一般用於付費購買次數,投票等場景使用.

3.1 內建頻率類

3.1.1 AnonRateThrottle

限制所有匿名未認證使用者,使用IP區分使用者。

使用DEFAULT_THROTTLE_RATES['anon']來設定頻次

-views.py
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import BasicAuthentication
class TestView4(APIView):
    authentication_classes=[]
    permission_classes = []
    def get(self,request,*args,**kwargs):
        return Response('我是未登入使用者')

# 全域性使用
-settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '3/m',# 使用 `second`, `minute`, `hour` 或`day`來指明週期。
    }
}

# 區域性使用
-views.py
class TestView4(APIView):
   ...
   throttle_classes = [AnonRateThrottle]
   def get(self,request,*args,**kwargs):
        return Response('我是未登入使用者')

3.1.2 UserRateThrottle

限制認證使用者,使用User id 來區分。

使用DEFAULT_THROTTLE_RATES['user']來設定頻次

全域性:在setting中
  'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'user': '10/m',
    }
        
區域性配置:
在檢視類中配一個就行

3.1.3 ScopedRateThrottle

限制使用者對於每個檢視的訪問頻次,使用ip或user id。

class ContactListView(APIView):
    throttle_scope = 'contacts'
    ...

class ContactDetailView(APIView):
    throttle_scope = 'contacts'
    ...

class UploadView(APIView):
    throttle_scope = 'uploads'
    ...
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.ScopedRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'contacts': '1000/day',
        'uploads': '20/day'
    }
}

3.2 自定義根據IP限制

寫一個類,繼承SimpleRateThrottle,只需要重寫get_cache_key

from rest_framework.throttling import ScopedRateThrottle,SimpleRateThrottle

#繼承SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
    scope='luffy'
    def get_cache_key(self, request, view):
        return request.META.get('REMOTE_ADDR')   # 返回
    
# 區域性使用,全域性使用 
REST_FRAMEWORK={
    'DEFAULT_THROTTLE_CLASSES': (
        'utils.throttling.MyThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'luffy': '3/m'  # key要跟類中的scop對應
    },
}