DRF-認證許可權頻率

HammerZe發表於2022-04-03

DRF-認證許可權頻率

DRF 路由元件

前後端混合開發,可以通過HttpResponse物件來設定cookie進而校驗登入,現在前後端分離開發,用不到cookie,那麼該怎麼認證?DRF提供了認證的方法

我們知道在APIView執行的過程中,在dispatch方法中走了三大認證self.initial(request, *args, **kwargs)

def initial(self, request, *args, **kwargs):
	···
    self.perform_authentication(request)  # 認證
    self.check_permissions(request)    # 許可權
    self.check_throttles(request)    # 頻率

認證

需求

我們通過登入介面,來模擬認證登入,登入成功返回json字串,並且攜帶隨機字串(uuid模擬生成token),通過token隨機字串來判斷使用者是否登入,登入了就更新token,首次登入就存token;

分析

  • 建立User表
  • 建立UserToken表,和User一對一關係
  • 前端傳入使用者名稱,密碼
  • 資料庫取校驗使用者資訊
  • 校驗成功,Token表內新增一條記錄,返回給前端json格式字串,字串中帶一個隨機字串

登入介面

模型

from django.db import models


class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=16)
    user_type = models.IntegerField(choices=((1, '超級管理員'), (2, '普通管理員'), (3, '普通使用者')))

    def get_code(self):
        self.get_user_type_display()
        print(self.get_user_type_display())

class UserToken(models.Model):
    user = models.OneToOneField(to=User,on_delete=models.CASCADE)
    token = models.CharField(max_length=32)

檢視

from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from app01 import models


class UserView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        # 獲取資料
        username = request.data.get('username')
        password = request.data.get('password')
        user = models.User.objects.filter(username=username, password=password).first()
        if user:
            # 如果user有值說明登入成功,生產隨機字串,存入資料庫,如果重複登入那麼就更新隨機字串
            import uuid
            uuid_str = uuid.uuid4()
            # print(type(uuid_str)) # <class 'uuid.UUID'>
            token = str(uuid_str)
            # 如果存在就更新,如果不存在就新增,指定搜尋物件,然後defaults指定更新內容
            models.UserToken.objects.update_or_create(user=user,defaults={'token': token} )
            # 返回隨機字串
            return Response({'code': 100, 'msg': '登入成功', 'token': token})
        return Response({'code': 101, 'msg': '登入失敗,使用者名稱或密碼錯誤'})

路由

from django.contrib import admin
from django.urls import path,include
from app01 import views
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')
urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include(router.urls))
]

image

image

認證

  • 區域性使用:寫一個認證類,通過authentication_classes引數指定認證類

    class BookView(ModelViewSet):
        # 區域性使用
        authentication_classes = [LoginAuth,]
    
  • 全域性使用:寫一個認證類,settings.py配置,所有的檢視類生效

    REST_FRAMEWORK={
          "DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth",]
      		}
    
  • 區域性禁用:authentication_classes = []

我們知道平時生活中,有一些介面是認證後才能呼叫的,比如我們登入後才能檢視個人站點內容等···

在執行檢視函式之前執行了認證方法:self.perform_authentication(request)

這裡寫一個認證demo,只有登入過的才能檢視Book表

'''auth.py'''
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models

# 寫一個類繼承BaseAuthentication
class LoginAuth(BaseAuthentication):
    # 重寫authenticate方法
    def authenticate(self, request):
        # 獲取前端攜帶的token,token放在哪是自己規定的,比如從查詢引數中獲取
        token = request.query_params.get('token')
        # 比對隨機字串
        user_token = models.UserToken.objects.filter(token=token).first()
        if user_token:
            # 登入了,返回當前登入使用者和token
            return user_token.user,token
        else:
            # 沒有登入,拋異常
            raise AuthenticationFailed('您沒有登入,請登入')
            
'''serializer.py'''
from rest_framework import serializers
from app01 import models
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = '__all__'
'''models.py'''
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(decimal_places=2,max_digits=5)
    author = models.CharField(max_length=32)
'''urls.py'''
from django.contrib import admin
from django.urls import path,include
from app01 import views
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')
router.register('books',views.BookView,'books')
urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include(router.urls))
]


  • 返回的user_token和token值可以通過在檢視類裡重寫list方法拿到

    '''views.py'''
    from .auth import LoginAuth
    class BookView(ModelViewSet):
        # 區域性使用
        authentication_classes = [LoginAuth,]
        queryset = models.Book.objects.all()
        serializer_class = serializer.BookSerializer
    	
        def list(self, request, *args, **kwargs):
            print(request.user) # User object (1)
            print(request.user.username) # HammerZe
            print(request.auth) # de914129-2f08-41a4-a7a9-de289badb659
            return super().list(request, *args, **kwargs)
    
    

總結

  • 返回的第一個(user_token.user),給了request.user,就是當前登入使用者物件
  • 返回的第二個(token),給了request.auth,就是token串
  • 區域性禁用和全域性配置使用的時候要注意,全域性如果認證的時候是每個檢視函式都認證,就比如登入檢視認證登入,那麼就死迴圈了,不認證不能登入,就相當於做核酸需要核酸單···

許可權

和認證一樣,都是寫一個類去繼承,寫許可權繼承BasePermission,重寫has_permission方法,判斷如果有許可權,返回True,如果沒有許可權,返回False

然後區域性使用或者全域性使用,或區域性禁用

作用

  • 許可權控制可以限制使用者對於檢視的訪問和對於具體資料物件的訪問
  • 認證通過, 可以進行下一步驗證 (頻率認證)
  • 認證失敗, 丟擲許可權異常結果

使用

  • 區域性使用:permission_classes = [UserPermission, ]

  • 全域性使用:

    REST_FRAMEWORK={
    			"DEFAULT_PERMISSION_CLASSES":["app01.auth.UserPermission",]
    		}
    
  • 區域性禁用:permission_classes = []

需求

  • 認證登入成功後,普通使用者只能查詢一條或所有
  • 管理員登入後才能通過許可權認證進行修改,增加,刪除操作

許可權類

class UserPermission(BasePermission):
    def has_permission(self, request, view):
        # 沒有許可權的提示資訊
        self.message = '您是:%s,沒有許可權' % request.user.get_user_type_display()
        # 如果有許可權,返回True,沒有許可權返回False
        # 許可權類,在認證類之後,request.user有了當前登入使用者
        user_type = request.user.user_type
        print(user_type)
        if user_type < 3:  # 只要不是1,2,就沒有許可權
            return True
        else:
            return False

檢視

from .auth import LoginAuth, UserPermission
from rest_framework.mixins import RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin,ListModelMixin,CreateModelMixin
from rest_framework.viewsets import GenericViewSet

class BookView(RetrieveModelMixin,ListModelMixin,GenericViewSet):
    # 區域性使用,普通使用者登入後只能獲取一條或所有
    authentication_classes = [LoginAuth, ]
    queryset = models.Book.objects.all()
    serializer_class = serializer.BookSerializer

class BookDetailView(CreateModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet):
    # 區域性使用,普通使用者沒有許可權
    authentication_classes = [LoginAuth, ]
    permission_classes = [UserPermission, ]
    queryset = models.Book.objects.all()
    serializer_class = serializer.BookSerializer

路由

from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')
router.register('books',views.BookView,'books')
router.register('bookdetail',views.BookDetailView,'bookdetail')
urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include(router.urls))
]

image

總結

  • 5個介面分成了倆檢視類寫
  • BookView:獲取所有,獲取單條API
  • BookDetailView:刪除,修改,新增API
  • 這倆檢視都需要登入:authentication_classes = [LoginAuth, ]
  • BookView只要登陸就可以操作
  • BookDetailView必須有許可權才能,加了一個許可權,permission_classes = [UserPermission, ]

步驟

  • 第一步:寫一個類,繼承BasePermission,重寫has_permission,判斷如果有許可權,返回True,如果沒有許可權,返回False
  • 第二步:區域性使用和全域性使用

注意

  • 如果使用ModelViewSet快速寫五個介面,那麼在驗證認證和許可權的時候就會錯亂,獲取和修改等操作都在一個檢視裡了,分開寫會好一點

頻率

作用

  • 限制檢視介面被訪問的頻率次數
  • 限制條件 : IP、ID、唯一鍵
  • 頻率週期 : 時(h)、分(m)、秒(s)
  • 頻率次數 : [num] / s
  • 沒有達到限制頻率可正常訪問介面
  • 達到了頻率限制次數, 在限制時間內不能進行訪問, 超過時間後可以正常訪問

使用

頻率類

# 頻率類
class IPThrottle(SimpleRateThrottle):
    scope = 'ip'

    # get_cache_key返回什麼就以什麼方法做限制,限制條件必須唯一,比如使用者id
    def get_cache_key(self, request, view):
        # 限制ip地址,從request.META字典中獲取ip
        '''
        request.META:請求頭中的資料
        '''
        return request.META.get('REMOTE_ADDR')  # 客戶端ip

配置檔案

REST_FRAMEWORK={
    'DEFAULT_THROTTLE_RATES': {
        'ip': '3/m'  # minute_3是scope的字串,一分鐘訪問3次
}

區域性使用

class BookView(RetrieveModelMixin, ListModelMixin, GenericViewSet):
    authentication_classes = [LoginAuth, ] # 登入認證
    permission_classes = [UserPermission, ] # 許可權限制
    throttle_classes = [IPThrottle, ]  # 頻率限制
    
    queryset = models.Book.objects.all()
    serializer_class = serializer.BookSerializer

全域性使用

REST_FRAMEWORK={

    'DEFAULT_THROTTLE_CLASSES': (  # 全域性配置頻率類
        'app01.auth.IPThrottle'
    ),
  		}

image

總結

  1. 寫一個類,繼承SimpleRateThrottle,重寫類屬性scope,scope值自定義,配置檔案中一致就行,重寫get_cache_key方法,返回什麼限制什麼
  2. 在配置檔案中配置,限制頻率
  3. 區域性/全域性使用

認證許可權頻率+五個介面

模型

from django.db import models


# Create your models here.


class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=16)
    user_type = models.IntegerField(choices=((1, '超級管理員'), (2, '普通管理員'), (3, '普通使用者')))


class UserToken(models.Model):
    user = models.OneToOneField(to=User,on_delete=models.CASCADE)
    token = models.CharField(max_length=32)

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(decimal_places=2,max_digits=5)
    author = models.CharField(max_length=32)

檢視

from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet

from app01 import models
from app01 import serializer


class UserView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        # 獲取資料
        username = request.data.get('username')
        password = request.data.get('password')
        user = models.User.objects.filter(username=username, password=password).first()
        if user:
            # 如果user有值說明登入成功,生產隨機字串,存入資料庫,如果重複登入那麼就更新隨機字串
            import uuid
            uuid_str = uuid.uuid4()
            # print(type(uuid_str)) # <class 'uuid.UUID'>
            token = str(uuid_str)
            # 如果存在就更新,如果不存在就新增,指定搜尋物件,然後defaults指定更新內容
            models.UserToken.objects.update_or_create(user=user, defaults={'token': token})
            # 返回隨機字串
            return Response({'code': 100, 'msg': '登入成功', 'token': token})
        return Response({'code': 101, 'msg': '登入失敗,使用者名稱或密碼錯誤'})


from .auth import LoginAuth, UserPermission, IPThrottle

from rest_framework.mixins import RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin, ListModelMixin, \
    CreateModelMixin
from rest_framework.viewsets import GenericViewSet


class BookView(RetrieveModelMixin, ListModelMixin, GenericViewSet):
    # 區域性使用,普通使用者登入後只能獲取一條或所有
    authentication_classes = [LoginAuth, ]
    throttle_classes = [IPThrottle, ]
    queryset = models.Book.objects.all()
    serializer_class = serializer.BookSerializer


class BookDetailView(CreateModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet):
    # 區域性使用,普通使用者沒有許可權
    authentication_classes = [LoginAuth, ]
    permission_classes = [UserPermission, ]
    queryset = models.Book.objects.all()
    serializer_class = serializer.BookSerializer

序列化器

from rest_framework import serializers
from app01 import models
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = '__all__'

認證許可權頻率類

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import BasePermission
from rest_framework.throttling import SimpleRateThrottle

from app01 import models


# 認證類
class LoginAuth(BaseAuthentication):
    # 重寫authenticate方法
    def authenticate(self, request):
        # 獲取前端攜帶的token,token放在哪是自己規定的,比如從查詢引數中獲取
        token = request.query_params.get('token')
        # 比對隨機字串
        user_token = models.UserToken.objects.filter(token=token).first()
        if user_token:
            # 登入了,返回當前登入使用者和token
            return user_token.user, token
        else:
            # 沒有登入,拋異常
            raise AuthenticationFailed('您沒有登入,請登入')


# 許可權類
class UserPermission(BasePermission):
    def has_permission(self, request, view):
        # 沒有許可權的提示資訊
        self.message = '您是:%s,沒有許可權' % request.user.get_user_type_display()
        # 如果有許可權,返回True,沒有許可權返回False
        # 許可權類,在認證類之後,request.user有了當前登入使用者
        user_type = request.user.user_type
        print(user_type)
        if user_type < 3:  # 只要不是1,2,就沒有許可權
            return True
        else:
            return False

# 頻率類
class IPThrottle(SimpleRateThrottle):
    scope = 'ip'

    # get_cache_key返回什麼就以什麼方法做限制,限制條件必須唯一,比如使用者id
    def get_cache_key(self, request, view):
        # 限制ip地址,從request.META字典中獲取ip
        '''
        request.META:請求頭中的資料
        '''
        return request.META.get('REMOTE_ADDR')  # 客戶端ip

配置檔案

REST_FRAMEWORK={
    'DEFAULT_THROTTLE_RATES': {
        'ip': '3/m'  # minute_3是scope的字串,一分鐘訪問3次
},

路由


from django.contrib import admin
from django.urls import path,include
from app01 import views
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')
router.register('books',views.BookView,'books')
router.register('bookdetail',views.BookDetailView,'bookdetail')
urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include(router.urls))
]

相關文章