DRF之權限認證頻率組件

在編程的世界中,咱們認爲,用戶輸入的全部數據都是不可靠的,不合法的,直接使用用戶輸入的數據是不安全的,不只如此,咱們還須要控制用戶的訪問行爲,接下來,咱們要學習認證組件、權限組件、頻率組件。git

引入

同窗們,經過前面三節課的學習,咱們已經詳細瞭解了DRF提供的幾個重要的工具,DRF充分利用了面向對象編程的思路,對Django的View類進行了繼承,並封裝了其as_view方法和dispatch方法,隨後提供了幾個很是方便的編程工具,好比解析器、序列化。程序員

咱們經過解析器,能夠對來自客戶端的application/json數據進行解析,另外,經過序列化工具,咱們可以快速構建一套符合REST規範的api,隨後又經過DRF的mixin、view及viewset對這些接口邏輯進行優化。github

有了他們,程序員開發Web應用的效率大大提升了,雖然咱們也嘗試本身動手實現了這些功能,可是既然有了優秀的工具,咱們就沒必要費盡心思重複發明輪子。DRF並不只僅提供了這幾個工具,今天咱們就來繼續深刻學習DRF提供的一些其餘的工具。django

跟以往同樣,咱們不只僅要學會這些工具的使用方式,而且要深刻研究他們的源碼,但願你們在研究源碼的過程當中,可以對面向對象編程的思路有更加深入的認識。編程

固然,按照慣例,爲了方便學習新知識以及閱讀源碼,咱們先來複習回顧一下以前已經學習過的知識。json

今日概要

  • retrieve方法源碼剖析
  • 認證組件的使用方式及源碼剖析
  • 權限組件的使用方式及源碼剖析
  • 頻率組件的使用方式及源碼剖析

知識點複習回顧

  • 昨日回顧
  • Python邏輯運算
知識點複習回顧一:Python邏輯運算

有了前兩天的基礎,今天看源碼咱們就沒有那麼大的壓力了,所要複習的知識也僅僅只有一個,那就是Python的邏輯運算,固然,稍後還會有幾個簡單的知識點,就不單獨拿出來複習了。api

什麼是邏輯運算呢?就是and、or、not。not爲取反,比較簡單,而and和or表示經過運算,計算表達式的布爾值,判斷最終結果爲真即止瀏覽器

  • and:x and y 表示布爾與,意爲,判斷and運算以後的最終結果,爲真即止,and運算必須表達式兩端全部值均爲真才能肯定最終結果,必須全部值都爲真
  • or:x and y 筆試布爾或,意爲,判斷or運算以後的最終結果,爲真即止,or運算遇到真即返回,即有一個真值便可。
  • not x:取反

看下面的代碼吧:安全

1
2
3
4
x = 10 and 20 # x = 20
x = 0 and 20 # x = 0
x = 10 or 20 # x = 10
x = 0 or 20 # x = 20

好了,知識點複習就這麼多。服務器

今日詳細

mixin之retrieve源碼剖析

上節課,咱們分析過mixin中create方法的源碼,今天,create方法比較簡單,今天,咱們來分析分析retrieve方法的源碼,它比create方法稍微複雜一點點,複雜的地方在於如何獲取須要操做的那條數據,由於咱們知道,咱們傳遞給不一樣的視圖類的全部方法都是同樣的,惟一變化的兩個是queryset和serializer_classes。

好了,廢話很少說,下面來分析一下:

  • Django程序啓動,開始初始化,獲取配置信息,獲取視圖類並加載到內存中,獲取url及視圖類的對應關係
  • 開始綁定視圖類和url的對應關係,執行as_view()方法
  • as_view()方法被執行的時候傳遞了參數,爲字典形式:{ 「get」: 「retrieve」, 「delete」: 「destroy」, 「put」: 「update」 }
  • 上一步中執行as_view()方法傳遞參數的目的是爲了完成優化,將delete請求方式從新命名爲不一樣的函數
  • ViewSetMixin類重寫了as_view()方法,也就是在這個地方將幾個函數從新綁定,它並無重寫dispatch方法
  • 該方法返回視圖函數view,注意在這個函數中有一個行 self = cls(**initkwargs), cls是視圖類,執行視圖函數時self就指向視圖函數的實例對象
  • 等待客戶端請求
  • 請求到來,開始執行視圖函數,注意,調用視圖函數時的方式是view(request),而若是url帶有參數,調用方式爲view(request, xxx=id)的形式
  • 顯然,咱們有命名參數(?P\d+),因此此時的調用方式爲view(request, pk=id)
  • 視圖函數中有一行self.kwargs = kwargs,因此pk已經被視圖函數找到了
  • 視圖函數返回self.dispatch(),開始執行dispatch方法,注意self是視圖類的實例化對象(每一個請求都被封裝爲一個對象)
  • dispatch開始執行get方法,注意此時的get方法會執行retrieve,覺得已經被重定向了
  • 開始執行retrieve,有一行instance = self.get_object(), 該方法在GenericAPIView中
  • 相當重要的是拿到self.kwargs中的pk關鍵字,而後從queryset中拿到想要的數據
  • 返回結果

從以上過程當中咱們能夠看出,最關鍵的一步就是對kwargs的封裝,這就是玄機所在,看到這裏,你對面向對象有了什麼新的領悟嗎?對於反射呢,有了跟多的思考和理解嗎?

若是沒有,不用着急,任何質的飛躍都須要量的積累,等咱們寫的多了,看得多了,天然就會突破瓶頸。

好了,同志們,這些內容算是對於視圖組件的進一步挖掘和吸取,至此,視圖組件咱們就差很少講完了。接下來,咱們要學習其餘工具了。

認證組件

好久好久之前,Web站點只是做爲瀏覽服務器資源(數據)和其餘資源的工具,甚少有什麼用戶交互之類的煩人的事情須要處理,因此,Web站點的開發這根本不關心什麼人在何時訪問了什麼資源,不須要記錄任何數據,有客戶端請求,我即返回數據,簡單方便,每個http請求都是新的,響應以後當即斷開鏈接。

而現在,互聯網的世界發生了翻天覆地的變化,用戶不只僅須要跟其餘用戶溝通交流,還須要跟服務器交互,無論是論壇類、商城類、社交類、門戶類仍是其餘各種Web站點,你們都很是重視用戶交互,只有跟用戶交互了,才能進一步留住用戶,只有留住了用戶,才能知道用戶需求,知道了用戶需求,纔會產生商機,有了用戶,就等於有了流量,纔可以騙到…額…抱歉…是融到錢,有了資金企業才能繼續發展,可見,用戶交互是很是重要的,甚至能夠說是相當重要的一個基礎功能。

而談到用戶交互,則必需要談到咱們今天所要學習的知識點,認證、權限和頻率。首先咱們來看看認證。

登陸成功後生成token

以前咱們學習過使用cookie和session兩種方式能夠保存用戶信息,這兩種方式不一樣的是cookie保存在客戶端瀏覽器中,而session保存在服務器中,他們各有優缺點,配合起來使用,可將重要的敏感的信息存儲在session中,而在cookie中能夠存儲不太敏感的數據。

今天咱們要講到的是使用token的方式,token稱之爲令牌。cookie、session和token都有其應用場景,沒有誰好誰壞,不過咱們開發數據接口類的Web應用,目前用token仍是比較多的。

token認證的大體步驟是這樣的:

  • 用戶登陸,服務器端獲取用戶名密碼,查詢用戶表,若是存在該用戶且第一次登陸(或者token過時),生成token,不然返回錯誤信息
  • 若是不是第一次登陸,且token未過時,更新token值

接下來,咱們建立兩個model,以下所示(token也能夠存儲在user表中,不過建議存儲在user表中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.db import models

# Create your models here.


class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type_entry = (
(1, 'Delux'),
(2, 'SVIP'),
(3, "VVIP")
)
user_type = models.IntegerField(choices=user_type_entry)
address = models.CharField(max_length=32)

def __str__(self):
return self.username


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

咱們無需實現get方法,由於涉及登陸認證,全部寫post方法接口,登陸都是post請求,視圖類以下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from django.http import JsonResponse

from rest_framework.views import APIView

from .models import User, Book, UserToken
from .utils import get_token


class UserView(APIView):

def post(self, request):
response = dict()
try:
username = request.data['username']
password = request.data['password']

user_instance = User.objects.filter(
user_name=username,
password=password
).first()

if user_instance:
access_token = get_token.generater_token()

UserToken.objects.update_or_create(user=user_instance, defaults={
"token": access_token
})
response["status_code"] = 200
response["status_message"] = "登陸成功"
response["access_token"] = access_token
response["user_role"] = user_instance.get_user_type_display()
else:
response["status_code"] = 201
response["status_message"] = "登陸失敗,用戶名或密碼錯誤"
except Exception as e:
response["status_code"] = 202
response["status_message"] = str(e)

return JsonResponse(response)

簡單寫了個獲取隨機字符串的方法用來生成token值:

1
2
3
4
5
6
7
# -*- coding: utf-8 -*-
import uuid


def generater_token():
random_str = ''.join(str(uuid.uuid4()).split('-'))
return random_str

以上就是token的簡單生成方式,固然,在生產環境中不會如此簡單,關於token也有相關的庫,好了,咱們構造幾條數據以後,能夠經過POSTMAN工具來建立幾個用戶的token信息。

接下來,如何對已經登陸成功的用戶實現訪問受權呢?也就是說,只有登陸過的用戶(有token值)才能訪問特定的數據,該DRF的認證組件出場了。

DRF認證組件使用

首先,咱們來看一看,DRF認證組件的使用方式,首先,咱們必須新建一個認證類,以後的認證邏輯就包含在這個類裏面:

1
2
3
4
5
6
7
8
9
10
11
12
13
class UserAuth(object):

def authenticate_header(self, request):
pass

def authenticate(self, request):
user_post_token = request.query_params.get('token')

token_object = UserToken.objects.filter(token=user_post_token).first()
if token_object:
return token_object.user.username, token_object.token
else:
raise APIException("認證失敗")

實現方式看上去很是簡單,到token表裏面查看token是否存在,而後根據這個信息,返回對應信息便可,而後,在須要認證經過才能訪問的數據接口裏面註冊認證類便可:

1
2
3
4
5
6
class BookView(ModelViewSet):

authentication_classes = [UserAuth, UserAuth2]

queryset = Book.objects.all()
serializer_class = BookSerializer

至於爲何這麼寫,接下來,咱們一塊兒分析源碼,你們就都很是清楚了。

DRF認證源碼剖析

前面的步驟都差很少,咱們來看有差異的地方,咱們說,request對象是APIView重寫的,這個是在dispatch方法裏面實現的,繼續日後看dispatch方法,咱們會看到self.initial方法,就是在這個方法裏面,咱們會看到認證、權限、頻率幾個組件的實現:

  • 執行self.initial()方法
  • 執行self.perform_authentication(request),方法,注意,新的request對象被傳遞進去了
  • 該方法只有一行request.user,根據以前的經驗,解析器(request.data),咱們知道這個user確定也是request對的一個屬性方法
  • 所料不錯,該方法繼續執行self._authenticate(),注意此時的self是request對象
  • 該方法會循環self.authenticators,而這個變量是在從新實例化request對象時經過參數傳遞的
  • 傳遞該參數是經過get_authenticatos()的返回值來肯定的,它的返回值是
    • [ auth for auth in self.authentication_classes ]
    • 也就是咱們的BookView裏面定義的那個類變量,也就是認證類
  • 一切都明朗了,循環取到認證類,實例化,而且執行它的authenticate方法
    • 這就是爲何認證類裏面須要有該方法
    • 若是沒有該方法,認證的邏輯就沒辦法執行
    • 至於類裏面的header方法,照着寫就行,有興趣的能夠研究源碼,這裏就不細究了
  • 該方法若是執行成功就返回一個元組,執行完畢
    • 若是失敗,它會捕捉一個APIException
    • 若是咱們不但願認證經過,能夠raise一個APIException

這就是認證組件的實現方式,很是簡單。

多個認證類的實現

而且,咱們還能夠指定多個認證類,只是須要注意的是,若是須要返回什麼數據,請在最後一個認證類中返回,由於若是在前面返回,在self._authentication()方法中會對返回值進行判斷,若是不爲空,認證的過程就會停止,多個認證類的實現方式以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class UserAuth2(object):

def authenticate(self, request):
raise APIException("認證失敗")


class UserAuth(object):

def authenticate_header(self, request):
pass

def authenticate(self, request):
user_post_token = request.query_params.get('token')

token_object = UserToken.objects.filter(token=user_post_token).first()
if token_object:
return token_object.user.username, token_object.token
else:
raise APIException("認證失敗")


class BookView(ModelViewSet):

authentication_classes = [UserAuth, UserAuth2]

若是不但願每次都寫那個無用的authenticate_header方法,咱們能夠這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from rest_framework.authentication import BaseAuthentication

class UserAuth2(BaseAuthentication):

def authenticate(self, request):
raise APIException("認證失敗")


class UserAuth(BaseAuthentication):

def authenticate(self, request):
user_post_token = request.query_params.get('token')

token_object = UserToken.objects.filter(token=user_post_token).first()
if token_object:
return token_object.user.user_name, token_object.token
else:
raise APIException("認證失敗")

繼承BaseAuthentication類便可。

全局認證

若是但願全部的數據接口都須要認證怎麼辦?很簡單,仍是根據以前的經驗,就是這句代碼:

1
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES

若是認證類本身沒有authentication_classes,就會到settings中去找,經過這個機制,咱們能夠將認證類寫入到settings文件中便可實現全局認證:

1
2
3
4
5
6
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'authenticator.utils.authentication.UserAuth',
'authenticator.utils.authentication.UserAuth2',
),
}

好了,認證到這裏就差很少了。接下來繼續介紹權限組件

權限組件

與認證組件幾乎差很少,咱們直接看使用方式吧

權限組件使用

定義權限類:

1
2
3
4
5
6
7
class UserPerms():
message = "您沒有權限訪問該數據"
def has_permission(self, request, view):
if request.user.user_type > 2:
return True
else:
return False

一樣的邏輯,一樣的方式,只是執行權限的方法名與執行認證的方法名不同而已,名爲has_permission,而且須要將當前的視圖類傳遞給該方法。

視圖類中加入permission_classes變量:

1
2
3
4
5
6
7
class BookView(ModelViewSet):

authentication_classes = [UserAuth]
permission_classes = [UserPerms2]

queryset = Book.objects.all()
serializer_class = BookSerializer
權限組件源碼剖析

權限組件的源碼與認證組件是同樣的。

頻率組件

使用自定義方式實現對ip地址進行訪問頻率控制

使用方式介紹,上面兩個組件也是幾乎同樣,只是用來作判斷的邏輯不同而已,下面是做業的答案:

throttles.py(該方式沒有DRF提供的方式簡潔)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import time
import math

from rest_framework import exceptions


class MyException(exceptions.Throttled):
default_detail = '鏈接次數過多'
extra_detail_plural = extra_detail_singular = '請在{wait}秒內訪問'

def __init__(self, wait=None, detail=None, code=None):
super().__init__(wait=wait, detail=detail, code=code)


class VisitThrottle():
user_visit_information = dict()
visited_times = 1
period = 60
allow_times_per_minute = 5
first_time_visit = True

def allow_request(self, request, view):
self.request_host = request_host = request.META.get("REMOTE_ADDR")
current_user_info = self.user_visit_information.get(request_host, None)

if not self.__class__.first_time_visit:
self.user_visit_information[request_host][0] += 1
current_visit_times = self.user_visit_information[request_host][0]

if current_visit_times > self.allow_times_per_minute:
if self._current_time - current_user_info[1] <= self.period:
if len(current_user_info) > 2:
current_user_info[2] = self._time_left
else:
current_user_info.append(self._time_left)

view.throttled = self.throttled
return None
else:
self.__class__.first_time_visit = True

if self.first_time_visit:
self.__class__.first_time_visit = False
self._initial_infomation()

return True

def wait(self):
return self.period - self.user_visit_information[self.request_host][2]

def throttled(self, request, wait):
raise MyException(wait=wait)

@property
def _current_time(self):
return time.time()

@property
def _time_left(self):
return math.floor(self._current_time - self.user_visit_information.get(self.request_host)[1])

def _initial_infomation(self):
self.user_visit_information[self.request_host] = [self.visited_times, self._current_time]

視圖類中:

1
2
3
4
class BookView(ModelViewSet):
throttle_classes = [ VisitThrottle ]
queryset = Book.objects.all()
serializer_class = BookSerializer
使用DRF簡單頻率控制實現對用戶進行訪問頻率控制

局部訪問頻率控制

1
2
3
4
5
6
7
8
from rest_framework.throttling import SimpleRateThrottle


class RateThrottle(SimpleRateThrottle):
rate = '5/m'

def get_cache_key(self, request, view):
return self.get_ident(request)

rate表明訪問評率,上面表示每分鐘五次,get_cache_key是必須存在的,它的返回值告訴當前頻率控制組件要使用什麼方式區分訪問者(好比ip地址)。

以後在視圖中使用便可:

1
2
3
4
5
6
7
8
9
from .utils.throttles import RateThrottle

# Create your views here.


class BookView(ModelViewSet):
throttle_classes = [ RateThrottle ]
queryset = Book.objects.all()
serializer_class = BookSerializer
全局訪問頻率控制

首先定義一個頻率控制類,而且必須繼承SimpleRateThrottle這個類,它是DRF提供的一個方便的頻率控制類,請看下面的代碼:

1
2
3
4
5
class RateThrottle(SimpleRateThrottle):
scope = "visit_rate"

def get_cache_key(self, request, view):
return self.get_ident(request)

另外,咱們須要在全局配置頻率控制參數

1
2
3
4
5
6
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": ('throttler.utils.throttles.RateThrottle',),
"DEFAULT_THROTTLE_RATES": {
"visit_rate": "5/m"
}
}

這樣就實現了,每分鐘最多五次訪問的邏輯。

今日總結

  • retrieve方法源碼剖析
  • 認證組件的使用方式及源碼剖析
  • 權限組件的使用方式及源碼剖析
  • 頻率組件的使用方式及源碼剖析

                                                    轉自:pizzali

相關文章
相關標籤/搜索