django-rest-framework 基礎三 認證、許可權和頻率

Hans_Wang發表於2022-05-11

django-rest-framework 基礎三 認證、許可權和頻率

1. 認證

登入介面: 登入成功只要給前端返回json格式字串,這個字串中帶一個隨機字串(可以使用uuid生成)

登入介面步驟:

前端傳入使用者名稱和密碼,然後去user表中查詢,能找到說明使用者和密碼沒問題,登入成功,然後在userToken表中存一條記錄,說明登入過了,再返回前端一個json字串

1.1 登入介面

models.py

from django.db import models

# Create your models here.

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

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


views.py

from django.shortcuts import render

# Create your views here.
from rest_framework.viewsets import GenericViewSet
from authenticated.models import User,UserToken
from rest_framework.response import Response
from rest_framework.decorators import action
import uuid

class UserView(GenericViewSet):

    @action(methods=['POST'], detail=False)
    def login(self,request):
        username = request.data.get("username")
        password = request.data.get("password")
        user = User.objects.filter(username=username, password=password).first()

        if not user:
            return  Response({"code":1001,"msg":"使用者名稱或密碼錯誤"})

        token = str(uuid.uuid4())  # 獲取一個不重複的值,做唯一標識
        # userToken表中有就更新,沒有就建立
        #UserToken.objects.update_or_create(user=user, defaults={'token': token})
        UserToken.objects.update_or_create(user=user, defaults={'userToken': token})
        # 返回資訊,並帶著token
        return Response({"code":1000,"msg":"登入成功",'userToken': token}) 

urls.py

from django.contrib import admin
from django.urls import path,include
from authenticated import views
from rest_framework import routers

router = routers.SimpleRouter()
router.register('user',views.UserView, "user")

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls))
]

訪問:

post請求: http://127.0.0.1:8000/user/login/

1.2 認證

有了登入介面,就可以實現認證,如果要呼叫別的介面必須要先登入才可以。

例如有個圖書的表有五個介面,要訪問圖書的五個介面必須就登入。

步驟:

1. 先寫一個類,繼承BaseAuthentication,並重寫authenticate方法,在方法中校驗是否登入,登入則返回兩個值,沒有則拋異常
from rest_framework.authentication import BaseAuthentication
class xxxx(BaseAuthentication):
	def authenticate(self, request):
        
2.  全域性配置和區域性配置
全域性配置:
settings.py中
    REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["authenticated.authentication.LoingAuth"]
}
區域性配置:
在檢視類中:
authentication_classes = [xxxx,]

禁止區域性署配置:
authentication_classes = []


登入則返回兩個值:
    request.user 當前登入的使用者
    request.auth 為當前登入使用者的token

示例:

models.py

from django.db import models

# Create your models here.

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

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


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

認證功能,在app中新建authentication.py

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

class LoingAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        user_token = UserToken.objects.filter(userToken=token).first()
        if not user_token:
            raise AuthenticationFailed("請先登入")
        return user_token.user, token

views.py

from django.shortcuts import render

# Create your views here.
from rest_framework.viewsets import GenericViewSet
from authenticated.models import User,UserToken
from rest_framework.response import Response
from rest_framework.decorators import action
import uuid


from rest_framework.viewsets import ModelViewSet
from authenticated.models import Book
from authenticated.serializer import BookSerializer
from authenticated.authentication import LoingAuth

class UserView(GenericViewSet):

    @action(methods=['POST'], detail=False)
    def login(self,request):

        username = request.data.get("username")
        password = request.data.get("password")
        user = User.objects.filter(username=username, password=password).first()

        if not user:
            return  Response({"code":1001,"msg":"使用者名稱或密碼錯誤"})

        token = str(uuid.uuid4())
        # userToken表中有就更新,沒有就建立
        #UserToken.objects.update_or_create(user=user, defaults={'token': token})
        UserToken.objects.update_or_create(user=user, defaults={'userToken': token})
        return Response({"code":1000,"msg":"登入成功",'token': token})



class BookView(ModelViewSet):
    # 只對BookView單獨進行認證(區域性配置)
    authentication_classes = [LoingAuth,]
    queryset = Book.objects.all()
    serializer_class = BookSerializer

路由urls.py

from django.contrib import admin
from django.urls import path,include
from authenticated import views
from rest_framework import routers

router = routers.SimpleRouter()
router.register('user',views.UserView, "user")
router.register('books', views.BookView,"books")

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls))
]

全域性配置settings.py

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["authenticated.authentication.LoingAuth"],
}
# authenticated.authentication.LoingAuth 為寫的認證的類
# 全域性配置後區域性配置的就可以取消了。
# 但是全域性配置後如果有些類不想讓它有認證,比如登入介面,它不能有認證否則就死迴圈了。

# 全域性配置後,單獨取消某一個介面的認證: 
class UserView(ViewSet):
	authentication_classes = []  # 讓它等於空就可以了。

2. 許可權

所有的介面必須登入後才能訪問(給每個檢視加認證),登入成功後如果是普通使用者則只可檢視全部或單條資料。如果想要增刪改必須是管理員或超級管理員。

演示可以把五個介面寫成兩個檢視:

在應用目錄下建立permission.py檔案

permission.py

from rest_framework.permissions import BasePermission


class userPermission(BasePermission):
    def has_permission(self, request, view):
        user_type = request.user.user_type
        # (1, "超級管理員"),(2,"管理員"),(3,"普通使用者"),如果小於3說明是管理或超管使用者
        if user_type <3:
            return True
        else:
            return False

檢視views.py

from rest_framework.viewsets import GenericViewSet
from authenticated.models import User, UserToken
from authenticated.models import Book
from authenticated.serializer import BookSerializer
from authenticated.authentication import LoingAuth
from rest_framework.mixins import CreateModelMixin, ListModelMixin, DestroyModelMixin, UpdateModelMixin, \
    RetrieveModelMixin


    #使用者登入介面此處省略(見上面1.2認證)
    
    
# 檢視全部和單條。只要登入了誰都可以訪問
class BookView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    authentication_classes = [LoingAuth, ]
    queryset = Book.objects.all()
    serializer_class = BookSerializer


#  只有管理和超管使用者可以 建立、修改、新增

from authenticated.permission import userPermission


class BookDetailView(GenericViewSet, CreateModelMixin, UpdateModelMixin, DestroyModelMixin):
    authentication_classes = [LoingAuth]
    permission_classes = [userPermission]
    queryset = Book.objects.all()
    serializer_class = BookSerializer

路由 urls.py:

from django.contrib import admin
from django.urls import path,include
from authenticated import views
from rest_framework import routers

router = routers.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))
]

全域性配置settings.py

REST_FRAMEWORK = {
   "DEFAULT_PERMISSION_CLASSES":["authenticated.permission.userPermission"],
}
# authenticated.permission.userPermission 為寫的許可權的類
# 全域性配置後區域性配置的就可以取消了。

# 全域性配置後,單獨取消某一個介面的許可權: 
class UserView(ViewSet):
	permission_classes = []  # 讓它等於空就可以了。

普通使用者訪問的時候會報沒有許可權

image-20220405011610943

2.1 許可權總結:

兩個檢視:

BookView:獲取所有,獲取單條

BookDetailView:刪除,修改,新增
上面兩個檢視都需要登入:authentication_classes = [LoginAuth, ]

BookDetailView必須有許可權才能,加了一個許可權,permission_classes = [UserPermission, ]

編寫許可權步驟

第一步:寫一個類,繼承BasePermission,重寫has_permission,判斷如果有許可權,返回True,如果沒有許可權,返回False
第二步:區域性配置和全域性配置
區域性配置
		class BookDetailView(GenericViewSet, CreateModelMixin, DestroyModelMixin, UpdateModelMixin):
    		permission_classes = [UserPermission, ]
    
全域性配置
    settings.py
  		REST_FRAMEWORK={
			"DEFAULT_PERMISSION_CLASSES":["authenticated.permission.userPermission",]
		}

3. 頻率

限制訪問的頻率

在應用目錄建立throttle.py檔案用來限制頻率

throttle.py

from rest_framework.throttling import SimpleRateThrottle

class IpThrottle(SimpleRateThrottle):
    scope = 'min_3'   # 在settings.py定義給哪個類限制的頻率

    # get_cache_key 返回什麼就以什麼做限制,現在是以IP做限制
    def get_cache_key(self, request, view):
        return request.META.get('REMOTE_ADDR')  # 返回的是客戶端的IP,以IP做限制
    	# return request.user.id # 返回已經登入的使用者的id,以使用者id做限制

views.py

from authenticated.throttle import IpThrottle

# 檢視全部和單條。只要登入了誰都可以訪問
class BookView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    authentication_classes = [LoingAuth, ]
    throttle_classes = [IpThrottle]  # 訪問BookView類做限制
    queryset = Book.objects.all()
    serializer_class = BookSerializer

settings.py

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES":{
        'min_3':'3/m',  
    },
}

# min_3 就是上面throttle.py.IpThrottle裡scope定義的,這個一定要和scope定義的一致
# 3/m 每一分鐘訪問3次 ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
# 如果throttle.py.IpThrottle裡還有別的限制的類,如果scope也是為min_3,那它也是每分鐘訪問3次的限制

超過3次就會報錯:

image-20220405020938862

上面的配置為區域性配置,也可以設定全域性配置

settings.py

REST_FRAMEWORK = {
   "DEFAULT_THROTTLE_CLASSES" : ["authenticated.throttle.IpThrottle"], # 全域性配置
    "DEFAULT_THROTTLE_RATES":{ 
        'min_3':'3/m',
    },
}
# 同樣設定了全域性,區域性的throttle_classes = [IpThrottle]就可以不寫了
# 如果只是某個類禁用:
throttle_classes = []

3.1 頻率總結

步驟:

第一步:寫一個類,繼承SimpleRateThrottle,重寫類屬性:scope,和get_cache_key方法
  	get_cache_key返回什麼,就以什麼做限制,
    scope配置檔案中要用
    
第二步:在配置檔案中配置
settings.py中
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES":{
        'min_3':'3/m',  # minute_3是scope的字串,一分鐘訪問3次
    },
}

第三步: 使用
1. 區域性使用--》檢視類中
  class BookView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    throttle_classes = [IPThrottle]

    
2. 全域性使用--配置檔案中
settings.py中
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES" : ["authenticated.throttle.IpThrottle"],
    "DEFAULT_THROTTLE_RATES":{
        'min_3':'3/m',  # minute_3是scope的字串,一分鐘訪問3次
    },
}