1. 程式人生 > >drf token重新整理配置、認證元件(使用)、許可權元件(使用)、頻率元件(使用)、異常元件(使用)

drf token重新整理配置、認證元件(使用)、許可權元件(使用)、頻率元件(使用)、異常元件(使用)

目錄

  • 一、特殊路由對映的請求
  • 二、token重新整理機制配置(瞭解)
  • 三、認證元件專案使用:多方式登入
    • 1、urls.py 路由
    • 2、views.py 檢視
    • 3、serializers.py 序列化
    • 4、models.py 表
  • 四、許可權元件專案使用:vip使用者許可權
    • 資料準備
    • 1、permissions.py 自定義許可權
    • 2、views.py 許可權檢視
    • 3、serializers.py 序列化
    • 4、urls.py 自定義路由
    • 5、setting.py 許可權配置
  • 五、頻率元件
    • 頻率限制原始碼分析
    • 重點
    • 系統頻率類
    • 自定義頻率類
      • 1、throttles.py 自定義頻率,和請求方式限制
      • 2、settings.py 頻率配置
      • 3、views.py 頻率檢視
  • 六、異常元件專案使用:記錄異常資訊到日誌檔案
    • exception.py 處理後端異常
    • settings.py 異常配置

一、特殊路由對映的請求

實現使用者中心資訊自查,不帶主鍵的get請求,走單查邏輯

urls.py

# 用路由元件配置,形成的對映關係是 /user/center/ => list  |  user/center/(pk)/ => retrieve
# router.register('user/center', views.UserCenterViewSet, 'center')

urlpatterns = [
    # ...
    # /user/center/ => 單查,不能走路由元件,只能自定義配置對映關係
    url('^user/center/$', views.UserCenterViewSet.as_view({'get': 'user_center'})),
]

views.py

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class UserCenterViewSet(GenericViewSet):
    permission_classes = [IsAuthenticated, ]
    queryset = models.User.objects.filter(is_active=True).all()
    serializer_class = serializers.UserCenterSerializer

    def user_center(self, request, *args, **kwargs):
        # request.user就是前臺帶token,在經過認證元件解析出來的,
        # 再經過許可權元件IsAuthenticated的校驗,所以request.user一定有值,就是當前登入使用者
        serializer = self.get_serializer(request.user)
        return Response(serializer.data)

二、token重新整理機制配置(瞭解)

drf-jwt直接提供重新整理功能

"""
1)運用在像12306這樣極少數安全性要求高的網站
2)第一個token由登入簽發
3)之後的所有正常邏輯,都需要傳送兩次請求,第一次是重新整理token的請求,第二次是正常邏輯的請求
"""

settings.py

import datetime

JWT_AUTH = {
    # 配置過期時間
    'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=5),

    # 是否可重新整理
    'JWT_ALLOW_REFRESH': True,
    # 重新整理過期時間
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}

urls.py

from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
urlpatterns = [
    url('^login/$', ObtainJSONWebToken.as_view()),  # 登入簽發token介面
    url('^refresh/$', RefreshJSONWebToken.as_view()),  # 重新整理toekn介面
]

Postman

# 介面:/api/refresh/
# 方法:post
# 資料:{"token": "登入簽發的token"}

三、認證元件專案使用:多方式登入

注意:登入是post請求

1、urls.py 路由

# 自定義登入(重點):post請求 => 查操作(簽發token返回給前臺) - 自定義路由對映
url('^user/login/$', views.LoginViewSet.as_view({'post': 'login'})),

2、views.py 檢視

# 重點:自定義login,完成多方式登入
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class LoginViewSet(ViewSet):
    # 登入介面,要取消所有的認證與許可權規則,也就是要做區域性禁用操作(空配置)
    authentication_classes = []
    permission_classes = []

    # 需要和mixins結合使用,繼承GenericViewSet,不需要則繼承ViewSet
    # 為什麼繼承檢視集,不去繼承工具檢視或檢視基類,因為檢視集可以自定義路由對映:
    #       可以做到get對映get,get對映list,還可以做到自定義(靈活)
    def login(self, request, *args, **kwargs):
        serializer = serializers.LoginSerializer(data=request.data, context={'request': request})  # 自定義序列化
        serializer.is_valid(raise_exception=True)  # 序列化校驗
        token = serializer.context.get('token')  # 獲取token
        return Response({"token": token})

3、serializers.py 序列化

from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler

# 重點:自定義login,完成多方式登入
class LoginSerializer(serializers.ModelSerializer):
    # 登入請求,走的是post方法,預設post方法完成的是create入庫校驗,所以唯一約束的欄位,會進行資料庫唯一校驗,導致邏輯相悖
    # 需要覆蓋系統欄位,自定義校驗規則,就可以避免完成多餘的不必要校驗,如唯一欄位校驗
    username = serializers.CharField()
    class Meta:
        model = models.User
        # 結合前臺登入佈局:採用賬號密碼登入,或手機密碼登入,佈局一致,所以不管賬號還是手機號,都用username欄位提交的
        fields = ('username', 'password')

    def validate(self, attrs):
        # 在全域性鉤子中,才能提供提供的所需資料,整體校驗得到user
        # 再就可以呼叫簽發token演算法(drf-jwt框架提供的),將user資訊轉換為token
        # 將token存放到context屬性中,傳給外來鍵檢視類使用
        user = self._get_user(attrs)
        payload = jwt_payload_handler(user)  # 內部提供的方法,生成tokrn
        token = jwt_encode_handler(payload)  # 內部提供的方法,生成token
        self.context['token'] = token       # 吧token放進context中,方便取
        return attrs

    # 多方式登入
    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        import re
        if re.match(r'^1[3-9][0-9]{9}$', username):
            # 手機登入
            user = models.User.objects.filter(mobile=username, is_active=True).first()
        elif re.match(r'^.+@.+$', username):
            # 郵箱登入
            user = models.User.objects.filter(email=username, is_active=True).first()
        else:
            # 賬號登入
            user = models.User.objects.filter(username=username, is_active=True).first()
        if user and user.check_password(password):
            return user

        raise ValidationError({'user': 'user error'})

4、models.py 表

from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):  # settings.py中要配置
    mobile = models.CharField(max_length=11, unique=True, verbose_name='行動電話')
    icon = models.ImageField(upload_to='icon', default='icon/default.png', verbose_name='頭像')

    class Meta:
        db_table = 'o_user'
        verbose_name_plural = '使用者表'

    def __str__(self):
        return self.username


class Book(models.Model):
    name = models.CharField(max_length=64, verbose_name='書名')

    class Meta:
        db_table = 'o_book'
        verbose_name_plural = '書表'

    def __str__(self):
        return self.name


class Car(models.Model):
    name = models.CharField(max_length=64, verbose_name='車名')

    class Meta:
        db_table = 'o_car'
        verbose_name_plural = '車表'

    def __str__(self):
        return self.name

四、許可權元件專案使用:vip使用者許可權

資料準備

"""
1)User表建立兩條資料
2)Group分組表建立一條資料,name叫vip
3)操作User和Group的關係表,讓1號使用者屬於1號vip組
"""

1、permissions.py 自定義許可權

from rest_framework.permissions import BasePermission

from django.contrib.auth.models import Group
class IsVipUser(BasePermission):
    def has_permission(self, request, view):
        if request.user and request.user.is_authenticated:  # 必須是合法使用者
            try:
                vip_group = Group.objects.get(name='vip')
                if vip_group in request.user.groups.all():  # 使用者可能不屬於任何分組
                    return True  # 必須是vip分組使用者
            except:
                pass

        return False

2、views.py 許可權檢視

from .permissions import IsVipUser
class CarViewSet(ModelViewSet):
    permission_classes = [IsVipUser] # 自定義校驗

    queryset = models.Car.objects.all()
    serializer_class = serializers.CarSerializer

3、serializers.py 序列化

class CarSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Car
        fields = ('name', )

4、urls.py 自定義路由

router.register('cars', views.CarViewSet, 'car')

5、setting.py 許可權配置

from rest_framework import settings
# drf配置(把配置放在最下方)
REST_FRAMEWORK = {
    # 自定義三大認證配置類們
    'DEFAULT_AUTHENTICATION_CLASSES':['rest_framework_jwt.authentication.JSONWebTokenAuthentication'], # 認證
    # 'DEFAULT_PERMISSION_CLASSES': [],         # 許可權
    # 'DEFAULT_THROTTLE_CLASSES': [],           # 頻率
  }  

五、頻率元件

頻率限制原始碼分析

重點

"""
1)如何自定義頻率類
2)頻率校驗的規則
3)自定義頻率類是最常見的:簡訊介面一分鐘只能發生一條簡訊
"""

系統頻率類

#1)UserRateThrottle: 限制所有使用者訪問頻率
#2)AnonRateThrottle:只限制匿名使用者訪問頻率

自定義頻率類

from rest_framework.throttling import SimpleRateThrottle
"""
1)自定義類繼承SimpleRateThrottle
2)設定類實現scope,值就是一個字串,與settings中的DEFAULT_THROTTLE_RATES進行對應
    DEFAULT_THROTTLE_RATES就是設定scope繫結的類的頻率規則:1/min 就代表一分鐘只能訪問一次
3)重寫 get_cache_key(self, request, view) 方法,指定限制條件
    不滿足限制條件,返回None:代表對這類請求不進行頻率限制
    滿足限制條件,返回一個字串(是動態的):代表對這類請求進行頻率限制
        簡訊頻率限制類,返回 "throttling_%(mobile)s" % {"mobile": 實際請求來的電話}
"""

1、throttles.py 自定義頻率,和請求方式限制

from rest_framework.throttling import SimpleRateThrottle
# 只限制查介面的頻率,不限制增刪改的頻率
class MethodRateThrottle(SimpleRateThrottle):
    scope = 'method'    # 系統的配置就放在配置檔案中
    def get_cache_key(self, request, view):
        # 只有對get請求進行頻率限制
        if request.method.lower() not in ('get', 'head', 'option'):
            return None

        # 區別不同的訪問使用者,之間的限制是不衝突的
        if request.user.is_authenticated:
            ident = request.user.pk
        else:
            # get_ident是BaseThrottle提供的方法,會根據請求頭,區別匿名使用者,
            # 保證不同客戶端的請求都是代表一個獨立的匿名使用者
            ident = self.get_ident(request)
        return self.cache_format % {'scope': self.scope, 'ident': ident}

2、settings.py 頻率配置

REST_FRAMEWORK = {
    #  ...
    # 頻率規則配置
    'DEFAULT_THROTTLE_RATES': {
        # 只能設定 s,m,h,d,且只需要第一個字母匹配就ok,m = min = maaa 就代表分鐘
        'user': '3/min',  # 配合drf提供的 UserRateThrottle 使用,限制所有使用者訪問頻率
        'anon': '3/min',  # 配合drf提供的 AnonRateThrottle 使用,只限制匿名使用者訪問頻率
        'method': '3/min', # 限制請求方式頻率
    },
}

3、views.py 頻率檢視

from .permissions import IsVipUser
from .throttles import MethodRateThrottle
class CarViewSet(ModelViewSet):
    permission_classes = [IsVipUser]    # 自定義是否是VIP校驗
    throttle_classes = [MethodRateThrottle] # 自定義頻率限制
    
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarSerializer

六、異常元件專案使用:記錄異常資訊到日誌檔案

exception.py 處理後端異常

主要:後端異常之後一定要新增日誌記錄。配合日誌一起使用

from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
def exception_handler(exc, context):
    # 只處理客戶端異常,不處理伺服器異常,
    # 如果是客戶端異常,response就是可以直接返回給前臺的Response物件
    response = drf_exception_handler(exc, context)

    if response is None:
        # 沒有處理的伺服器異常,處理一下
        # 其實給前臺返回 伺服器異常 幾個字就行了
        # 那我們處理異常模組的目的是 不管任何錯誤,都有必要進行日誌記錄(線上專案只能通過記錄的日誌查看出現過的錯誤)
        response = Response({'detail': '%s' % exc})

    # 需要結合日誌模組進行日誌記錄的:專案中講
    return response

settings.py 異常配置

REST_FRAMEWORK = {
    # ...
    # 異常模組
    # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',  # 原來的,只處理客戶端異常
    'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}