在編程的世界中,咱們認爲,用戶輸入的全部數據都是不可靠的,不合法的,直接使用用戶輸入的數據是不安全的,不只如此,咱們還須要控制用戶的訪問行爲,接下來,咱們要學習認證組件、權限組件、頻率組件。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的邏輯運算,固然,稍後還會有幾個簡單的知識點,就不單獨拿出來複習了。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