1. 程式人生 > 其它 >drf : 認證 頻率 許可權

drf : 認證 頻率 許可權

編寫登入功能引出認證,許可權,頻率:

前端通過介面測試工具Postman將使用者名稱和密碼通過HTTP請求傳送至Django框架

models.py

from django.db import models


# Create your models here.
class Books(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.DO_NOTHING)


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)


class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)


"""
使用者登入生成隨機字串,響應中攜帶字串,傳送至服務端後,服務端做邏輯判斷。
一般一對一為垂直分表。資料庫優化的手段
"""
class UserToken(models.Model):
    user = models.OneToOneField(to='User', on_delete=models.CASCADE)
    token = models.CharField(max_length=64)

模型層拓展:

欄位引數可以填寫函式名,但不是不能加括號。

views.py

from rest_framework.views import APIView
from . import models
import uuid
from rest_framework.response import Response


class UserAPIView(APIView):
    def post(self,request,*args,**kwargs):
        back_dic = {'code':200,'msg':''}
        username = request.data.get('username')
        password = request.data.get('password')
        # 過濾出user表中含有請求中攜帶的資料,username,password.
        user = models.User.objects.filter(username=username,password=password).first()
        print(user)

        if user:
            """
            有使用者的情況下
            登入成功之後生成token
            user=user,根據user查詢,查詢不到使用defaults新增,查詢到更新。
            """
            # 生成隨機字串,請求中攜帶隨機字元傳
        		token = str(uuid.uuid4())
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            back_dic['msg'] = '隨機字串更新'
            back_dic['token'] = token
        else:
            back_dic['code'] = 201
            back_dic['msg'] = '使用者名稱或者密碼錯誤'
        return Response(back_dic)

此時客戶端傳送post請求,後端驗證正確會產生一條新的token覆蓋原有的token。

認證,許可權,頻率所在位置。

APIView --> dispath(重寫了request方法) --> initial(認證,許可權,頻率)
    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        """認證"""
        self.perform_authentication(request)
        """許可權"""
        self.check_permissions(request)
        """頻率"""
        self.check_throttles(request)

認證類的編寫

之前提到過,如果程式碼不屬於檢視層就另開一個py檔案。

認證:只有攜帶token隨機字串才可以訪問某個路由。

urls.py

from app01 import views
from rest_framework.routers import SimpleRouter, DefaultRouter
from django.urls import path, include

router = SimpleRouter()
router.register('books', views.BookAPIView)

urlpatterns = [
    # path('books/',views.BookAPIView.as_view({'get':'login'})),
    path('', include(router.urls)),
    path('login/', views.UserAPIView.as_view())
]

views.py

from .auth import LoginAuth
from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ViewSetMixin


class BookAPIView(ViewSetMixin, ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BookSerializers
    # 增加登入認證
    authentication_classes = [LoginAuth]

auth.py

"""認證類"""
from rest_framework.authentication import BaseAuthentication
from . import models
from rest_framework.exceptions import AuthenticationFailed

class LoginAuth(BaseAuthentication):
    def authenticate(self,request):
        # 寫認證規則
        # 取出使用者攜帶的token
        # -注意:取get提交的資料,不要從request.GET中取,從request.query_params中取
        token = request.query_params.get('token')
        print(token)
        # 去資料庫查詢,token是否存在,存在表示登入了,不存在表示為登入
        user_token = models.UserToken.objects.filter(token=token).first()
        if user_token:
          	# 943ffc46-3826-47f9-a4de-ea9cd4bd23d6
            print(user_token.token)
            # User object (1),模型類物件
            print(user_token.user)
            # junjie
            print(user_token.user.username)
            return user_token.user,user_token.token
        raise AuthenticationFailed('您沒有登入,認證失敗')

此時想要訪問books必須攜帶token才可以訪問。

認證類的全域性和區域性使用

全域性配置:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':[
        'app01.auth.LoginAuth'
    ]
}

區域性禁用配置:

class UserAPIView(APIView):
    authentication_classes = []

原始碼分析:

# APIView--->dispathch方法的-----》self.initial(request, *args, **kwargs)(497行)---》APIView的initial方法----》有三句話
self.perform_authentication(request)  # 認證
self.check_permissions(request)       # 許可權
self.check_throttles(request)         # 頻率



# self.perform_authentication(request)----》新的request物件的.user(是個方法,包裝成了資料數屬性)---》新的Request類中找到了user方法---》self._authenticate()-----》Request類中的_authenticate
    def _authenticate(self):
        for authenticator in self.authenticators: # 配置在檢視類中認證類列表,例項化得到的物件
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return
            
            
 #  self.authenticators,self是Request類的物件,authenticators屬性是Request這個類在例項化的時候,傳入的

# Request類在例項化的時候程式碼
    return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),  # 要看的
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )


# self.get_authenticators()是APIView的方法
def get_authenticators(self):
   	 	# 列表推導式
        return [auth() for auth in self.authentication_classes]
    	# 返回結果是我們配在檢視類中自己寫的認證類列表的物件
        return [LoginAuth(),]
		

許可權元件

匯入模組

from rest_framework.permissions import BasePermission

auth.py

from rest_framework.permissions import BasePermission

class UserPermission(BasePermission):
    def has_permission(self, request, view):
        """
        認證類走完再走許可權類,所以request.user可以拿到當前登入使用者
        """
        if request.user.user_type == 1:
            return True
        return False

全域性配置使用:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.auth.UserPermission',
    ],
}

區域性使用:

class BookAPIView(ViewSetMixin, ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BookSerializers
    # 增加登入認證
    authentication_classes = [LoginAuth]
    # 增加許可權類
    permission_classes = [UserPermission]

區域性禁用:

    permission_classes = []

許可權類原始碼解讀

APIView --> dispath --> initial --> self.check_permissions(request) --》
def check_permissions(self, request):
  """
  for permission in self.get_permissions(),self.get_permissions()在哪?
  		通過原始碼可以看到是許可權類的全域性和區域性配置。區域性有優先區域性,區域性沒有定義取全域性,全域性沒有去內建。
      def get_permissions(self):
        return [permission() for permission in self.permission_classes]
  """
  for permission in self.get_permissions():
    """
    對應到auth.py中的許可權類,如果為True,程式碼正常執行,如果為False,執行self.permission_denied
    """
    if not permission.has_permission(request, self):
      self.permission_denied(
        request,
        """
        反射,現在auth.py中的許可權類找massage.
        """
        message=getattr(permission, 'message', None),
        code=getattr(permission, 'code', None)
      )
# message 自定義報錯資訊
def permission_denied(self, request, message=None, code=None):
    """
    If request is not permitted, determine what kind of exception to raise.
    """
    if request.authenticators and not request.successful_authenticator:
        raise exceptions.NotAuthenticated()
    raise exceptions.PermissionDenied(detail=message, code=code)

auth.py

class UserPermission(BasePermission):
    def has_permission(self, request, view):
        """
        認證類走完再走許可權類,所以request.user可以拿到當前登入使用者
        """
        if request.user.user_type == 1:
            return True
        return False

頻率元件的使用

頻率的作用: 可以對介面訪問的頻次進行限制,以減輕伺服器壓力。一般用於付費購買次數,投票等場景使用.

**匯入模組: **

from rest_framework.throttling import SimpleRateThrottle

auth.py,寫一個類,繼承基類,重寫get_cache_key方法

class MyThrottle(SimpleRateThrottle):
    # scope一定要寫,字串對應到settings.py中配置REST_FRAMEWORK。
    scope = 'ip_Throttle'

    def get_cache_key(self, request, view):
        # 請求頭所包含的資訊在META中
        # 127.0.0.1
        print(request.META.get('REMOTE_ADDR'))
        return request.META.get('REMOTE_ADDR')

settings.py,配置檔案中一定要配置。

REST_FRAMEWORK = {
	# (一分鐘訪問三次),key要跟類中的scope對應
    'DEFAULT_THROTTLE_RATES':{
        'ip_Throttle':'3/m'  
    }
}

settings.py ,全域性配置。

REST_FRAMEWORK = {
  	 # (一分鐘訪問三次),key要跟類中的scope對應
    'DEFAULT_THROTTLE_RATES':{
        'ip_Throttle':'3/m'  
    },
 		 # 全域性配置
    'DEFAULT_THROTTLE_CLASSES': (
        'app01.auth.MyThrottle',
    ),
}

views.py,檢視類中配置

class BookAPIView(ViewSetMixin, ListAPIView):
    # 區域性使用
    throttle_classes = [MyThrottle,]