1. 程式人生 > 其它 >drf之認證、許可權、限流、過濾

drf之認證、許可權、限流、過濾

目錄

drf之認證、許可權、限流、過濾

一 drf認證Authentication

1 drf認證功能介紹

0) 認證,頻率,許可權
1) 使用者是否登入到系統中
2) 後期基本上會用JWT的認證
3) 自定製的認證

2 認證功能原始碼分析

2.1 原始碼分析

1) APIView---》dispatch---》self.initial(request, *args, **kwargs)--》self.perform_authentication(request)---》Request.user--->self._authenticate(self):Request類的方法---》self.authenticators:Request類的屬性---》在Request物件例項化的時候傳入的----》Request在什麼時候例項化的?dispatch的時候---》APIView:self.get_authenticators()--》return [auth() for auth in self.authentication_classes]----》如果在自己定義的檢視類中寫了authentication_classes=[類1,類2]----》Request的self.authenticators就變成了我們配置的一個個類的物件

# # 原始碼分析詳解
# a) APIView---》
    @classmethod
    def as_view(cls, **initkwargs): 
        view = super().as_view(**initkwargs)

	# 呼叫view下的as_view()
    @classonlymethod
    def as_view(cls, **initkwargs):        
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)

# b) dispatch---》  # 呼叫了dispatch,APIView中有此方法,故是APIView中的dispatch方法
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling.        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request   # 對request進行了屬性的賦值

        try:
            self.initial(request, *args, **kwargs)   #  執行位置

# c) self.initial(request, *args, **kwargs)--》
    def initial(self, request, *args, **kwargs):        
        self.perform_authentication(request)  # 執行到此處
        self.check_permissions(request)
        self.check_throttles(request)

# d) self.perform_authentication(request)---》  # 執行
    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication will instead be performed lazily, the first time either
 `request.user` or `request.auth` is accessed.        """
        # 不是原生django的user,是新的Request物件的方法
        request.user

# e) Request.user--->  # Request下的user方法
	@property
    def user(self):
        """  Returns the user associated with the current request, as authenticated by the authentication classes provided to the request.   """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

# f) self._authenticate(self):Request類的方法---》
    def _authenticate(self):
        """ Attempt to authenticate the request using each authentication instance in turn. """
        for authenticator in self.authenticators:  # 是Request類的屬性
            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._not_authenticated()

# g) self.authenticators:  Request類的屬性---》  h) 在Request物件例項化的時候傳入的----》
    def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )
        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()

# i) Request在什麼時候例項化的?  dispatch開始的時候的時候---》
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling.        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)  # request在APIView的dispatch這裡例項化的

# j) APIView:self.get_authenticators()--》
    def initialize_request(self, request, *args, **kwargs):
        """       Returns the initial request object.        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

# k) return [auth() for auth in self.authentication_classes]----》
    def get_authenticators(self):
        """        Instantiates and returns the list of authenticators that this view can use.        """
        return [auth() for auth in self.authentication_classes]  # 一個列表推導式

# l) 如果在自己定義的檢視類中寫了authentication_classes=[類1,類2]----》
class APIView(View):
    # The following policies may be set at either globally, or per-view.
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

# m) Request的self.authenticators就變成了我們配置的一個個類的物件

2.2 Request類的: self._authenticate(self) 方法

2) self._authenticate(self):Request類的方法
def _authenticate(self):
     for authenticator in self.authenticators: # BookView中配置的一個個類的物件
            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

3) 只要在檢視類中配置authentication_classes = [MyAuthen.LoginAuth, ]
	就會執行上面的方法,執行認證

3 自定義認證類(重點)

from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import SessionAuthentication, BasicAuthentication

# 1 使用(GET方法)
1)-定義一個類,繼承BaseAuthentication
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get('token')
        res = models.UserToken.objects.filter(token=token).first()
        if res:
            return 元組
        else:
            raise AuthenticationFailed('您沒有登入')

2)-重寫authenticate方法

3)-區域性使用和全域性使用
	-區域性:在檢視類中配置(只要配置了,就是登入以後才能訪問,沒配置,不用登入就能訪問)
		authentication_classes = [MyAuthen.LoginAuth, ]
	-全域性
        REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.MyAuthen.LoginAuth", ]
        }
        
4)-注意:
	1) 認證類,認證通過可以返回一個元組,有兩個值,第一個值會給,request.user,第二個值會個request.auth
    2) 認證類可以配置多個,按照從前向後的順序執行,如果前面有返回值,認證就不再繼續往下走了

4 認證功能區域性使用和全域性使用

1) 全域性使用(所有介面,都需要登入才能訪問)
	-在配置檔案中
        REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.MyAuthen.LoginAuth", ]
        }

2) 區域性使用
	-在想區域性使用的檢視類上
	authentication_classes = [MyAuthen.LoginAuth,]

3) 區域性禁用
	-在想禁用的檢視類上
    authentication_classes = []

5 內建認證方案(需要配合許可權使用)

5.1 全域性使用

# 可以在配置檔案中配置全域性預設的認證方案
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',  # session認證
        'rest_framework.authentication.BasicAuthentication',   # 基本認證
    )
}

5.2 區域性使用

# 也可以在每個檢視中通過設定authentication_classess屬性來設定
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class ExampleView(APIView):
    # 類屬性
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    ...

5.3 認證失敗的返回值

# 認證失敗會有兩種可能的返回值:
-401 Unauthorized       # 未認證
-403 Permission Denied  # 許可權被禁止

二 def許可權Permissions

# 許可權控制可以限制使用者對於檢視的訪問和對於具體資料物件的訪問。
    -在執行檢視的dispatch()方法前,會先進行檢視訪問許可權的判斷
    -在通過get_object()獲取具體物件時,會進行模型物件訪問許可權的判斷

1 自定義許可權功能(重點)

1) 登入成功以後,超級使用者可以幹某些事,普通使用者不能幹---》超級使用者可以檢視某些介面,普通使用者不能檢視

2) 使用:寫一個類繼承BasePermission,重寫has_permission
    class SuperPermission(BasePermission):
        def has_permission(self, request, view):
            # Return `True` if permission is granted, `False` otherwise.
            # 超級使用者可以訪問,除了超級使用者以外,都不能訪問
            if request.user.user_type == '1':
                return True
            else:
                return False
       
3) 區域性使用和全域性使用
	-在想區域性使用的檢視類上
	permission_classes = [MyAuthen.SuperPermission]
    -全域性使用
      REST_FRAMEWORK = {
        "DEFAULT_PERMISSION_CLASSES": ["app01.MyAuthen.SuperPermission", ]
        }
     -區域性禁用
    permission_classes = []

2 許可權功能區域性使用和全域性使用

# 1 使用方式
1)-在想區域性使用的檢視類上
	permission_classes = [MyAuthen.SuperPermission]

2)-全域性使用
	REST_FRAMEWORK = {
        "DEFAULT_PERMISSION_CLASSES": ["app01.MyAuthen.SuperPermission", ]
        }

3)-區域性禁用
    permission_classes = []

3 內建的許可權類

# 內建許可權類
from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly
    - AllowAny          # 允許所有使用者
    - IsAuthenticated   # 僅通過認證的使用者
    - IsAdminUser       # 僅管理員使用者
    - IsAuthenticatedOrReadOnly  # 已經登陸認證的使用者可以對資料進行增刪改操作,沒有登陸認證的只能檢視資料。

4 許可權原始碼分析

4.1 原始碼分析

# 1 原始碼分析
	-APIView的dispatch---》APIView的initial---》APIView的check_permissions(request)
        for permission in self.get_permissions(): # 許可權類物件放到列表中
        if not permission.has_permission(request, self):
            self.permission_denied(
                 request,
                 message=getattr(permission, 'message', None),
                 code=getattr(permission, 'code', None)
                )

4.2 錯誤資訊的中文顯示

# 2 錯誤資訊的中文顯示
	在許可權類中加一個 message=字串  ————》message=getattr(permission, 'message', None),

5 模型層choice欄位使用(重點)

1) 模型表:Student表,寫介面應該選擇繼承哪個檢視類
2) 推薦使用自動生成路由的方式(繼承ViewSetMixin及它的字類)
3) 但是目前來說,你先實現功能即可(至於選擇哪個,慢慢體會)

4) choice的使用
	-在模型類中使用
    sex = models.SmallIntegerField(choices=((1, '男'), (2, '女'), (3, '未知')), default=1)
    -在檢視類中,在序列化類中
    	-get_欄位名_dispaly()的方法,該方法獲得choice欄位對應的資料

三 drf限流Throttling

1 自定義頻率類(分析,瞭解)

1.1 自定義頻率類用途及使用

1) 限制某個人,某個ip的訪問頻次

2) 自定義頻率類及使用
from rest_framework.throttling import BaseThrottle
class MyThrottle(BaseThrottle):
    VISIT_RECORD = {}  # 存使用者訪問資訊的大字典
    def __init__(self):
        self.history = None
    def allow_request(self,request,view):
        # 根據ip進行頻率限制,每分鐘只能訪問3次
        # 限制的邏輯
        '''
        #(1)取出訪問者ip
        #(2)判斷當前ip不在訪問字典裡,新增進去,並且直接返回True, 表示第一次訪問,在字典裡,繼續往下走
        #(3)迴圈判斷當前ip的列表,有值,並且當前時間減去列表的最後一個時間大於60s,把這種資料pop掉,這樣列表中只有60s以內的訪問時間,
        #(4)判斷,當列表小於3,說明一分鐘以內訪問不足三次,把當前時間插入到列表第一個位置,返回True,順利通過
        #(5)當大於等於3,說明一分鐘內訪問超過三次,返回False驗證失敗
        '''
        # (1)取出訪問者ip
        # print(request.META)
        ip = request.META.get('REMOTE_ADDR')
        import time
        ctime = time.time()
        # (2)判斷當前ip不在訪問字典裡,新增進去,並且直接返回True,表示第一次訪問
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip] = [ctime, ]
            return True
        self.history = self.VISIT_RECORD.get(ip)
        # (3)迴圈判斷當前ip的列表,有值,並且當前時間減去列表的最後一個時間大於60s,把這種資料pop掉,這樣列表中只有60s以內的訪問時間,
        while self.history and ctime - self.history[-1] > 60:
            self.history.pop()
        # (4)判斷,當列表小於3,說明一分鐘以內訪問不足三次,把當前時間插入到列表第一個位置,返回True,順利通過
        # (5)當大於等於3,說明一分鐘內訪問超過三次,返回False驗證失敗
        if len(self.history) < 3:
            self.history.insert(0, ctime)
            return True
        else:
            return False

    def wait(self):
        # 還剩多長時間能訪問
        import time
        ctime = time.time()
        return 60 - (ctime - self.history[-1])   

1.2 區域性使用與全域性使用

1)-區域性使用
	throttle_classes = [auth.MyThrottle,]

2)-全域性使用
	REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES':['app01.auth.MyThrottle',],
}

2 內建頻率類使用

2.1 內建頻率類

-BaseThrottle:      # 基類
-AnonRateThrottle:  # 限制匿名使用者的訪問次數
-SimpleRateThrottle:# 我們自定義擴寫它
-ScopedRateThrottle:
-UserRateThrottle:  # 限制登入使用者訪問次數

2.2 擴充套件內建頻率類(重點記住)

# 擴充套件內建頻率類(重點記住)
1)-寫一個類,繼承SimpleRateThrottle
    class MySimpleThrottle(SimpleRateThrottle):
        scope = 'xxx'
        def get_cache_key(self, request, view):
            #以ip限制
            return self.get_ident(request)

2)-setting.py中配置
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_RATES' : {
            'xxx':'10/m'  # key跟scope對應,value是一個時間
        }
    }

3)-區域性使用與全域性使用

2.3 原始碼分析

# 原始碼分析
-繼承SimpleRateThrottle---》allow_request(跟咱們寫的一樣)

2.4 其它內建頻率類

# 其它內建頻率類
1)-限制未登入使用者的頻率(AnonRateThrottle)(根據ip限制)
-使用:
	-區域性使用,全域性使用
	-setting.py中配置
	'DEFAULT_THROTTLE_RATES' : {
    'anon':'1/m'
	}

2)-限制登入使用者訪問次數 UserRateThrottle(根據使用者id限制)
-使用:
    -區域性使用,全域性使用
    -setting.py中配置
    'DEFAULT_THROTTLE_RATES' : {
        'user':'1/m'
    }

3)-ScopedRateThrottle(有興趣看一下,沒有就不看了) 

四 過濾Filter與排序

1 內建,第三方過濾功能(次重點)

1) 過濾:篩選查詢結果

2) 內建篩選的使用
	-在檢視類中配置
        filter_backends = [SearchFilter,]
        search_fields = ('name',) # 表模型中的欄位
    -查詢的時候
    	http://127.0.0.1:8000/students/?search=e
                
3) 第三方擴充套件的過濾功能
	-pip3 install django-filter  :最新版本(2.4.0)要跟django2.2以上搭配
    
    -在檢視類中配置
        filter_backends = [DjangoFilterBackend,]
    	filter_fields = ['name','age']
    -查詢的時候
    	http://127.0.0.1:8000/students/?name=lqz&age=18

2 排序功能(次重點)

-在檢視類中配置
    filter_backends = [OrderingFilter,]
    ordering_fields = ['id','age']

-查詢的時候
	http://127.0.0.1:8000/students/?ordering=-age
            
            
 ### 過濾後再排序
  -在檢視類中配置
    filter_backends = [OrderingFilter,DjangoFilterBackend]
    ordering_fields = ('id', 'age')
    filter_fields = ['name','age']

  -查詢的時候
	http://127.0.0.1:8000/students/?name=lqz&age=19&ordering=-age,-id

3 自定製過濾器

3.1 使用原理

1)-查詢所有才會有過濾---》list才需要過濾---》queryset = self.filter_queryset(self.get_queryset())---》GenericAPIView-->filter_queryset

3.2 使用方法

# 基於django-filter擴寫

1) 寫一個類MyFilter,繼承BaseFilterBackend

2) 重寫filter_queryset方法,在該方法內部進行過濾(自己設定的過濾條件)
    def filter_queryset(self, queryset):
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
            return queryset
# self.get_queryset() 就是 models.表名.objects.all()

3) 返回queryset物件(過濾後的queryset物件)

4) 配置在檢視類中
	filter_backends = [MyFilter,]

五 拓展

1) select_related的使用
# 基於物件的跨表查詢,可以減少查詢資料庫的次數
articleList=models.Article.objects.select_related("category").all()
for article_obj in articleList:
        #  Doesn't hit the database, because article_obj.category
        #  has been prepopulated in the previous query.
        #不再查詢資料庫,因為第一次查詢,資料已經填充進去了
        print(article_obj.category.title)
        
2) mysql的悲觀鎖和樂觀鎖

3) drf內部內建了一些認證類,分別幹了什麼事
	待填坑