Django Rest framework之認證的實現(xiàn)代碼
django rest framework 官網(wǎng)
在學(xué)習(xí)django rest framework(下面簡稱drf)之前需要知道
- 對RESTful API設(shè)計有一定了解
- 對django框架有一定認識,本身drf就是基于django做的
- 對python面向?qū)ο缶幊逃辛私猓╠rf會對一些原生的django類做封裝)
一、前言
在學(xué)習(xí)drf之前的時候,先簡單說一下需要的預(yù)備知識。在django中,路由匹配之后,會進行路由分發(fā),這個時候會有兩種選擇模式的選擇。也就是FBV與CBV。
1、FBV
fbv就是在url中一個路徑對應(yīng)一個函數(shù)
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.index) ]
在視圖函數(shù)中
def index(request): return render(request, 'index.html')
2、CBV
cbv就是在url中一個路徑對應(yīng)一個類,drf主要使用CBV
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.IndexView.as_view()) # 執(zhí)行類后面的as_view()方法,是父類里面的方法 ]
在視圖函數(shù)中
from django.views import View
class IndexView(View):
# 以get形式訪問會執(zhí)行g(shù)et函數(shù),一般情況下獲取數(shù)據(jù)
def get(self, *args, **kwargs):
return HttpResponse('666')
# 以post形式訪問的話會執(zhí)行post函數(shù),一般情況下發(fā)送數(shù)據(jù)
def post(self, *args, **kwargs):
return HttpResponse('999')
我們在路由匹配的時候看到url(r'^index/', views.IndexView.as_view()),那這個as_view()是什么,既然我們在視圖類中沒有定義這個as_view()方法,就應(yīng)該到父類(也就是IndexView的父類View)中看一下View。以下是django源碼,路徑是\django\views\generic\base.py,
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] # 支持的各種http方法
def __init__(self, **kwargs):
pass
@classonlymethod
def as_view(cls, **initkwargs): # url路由匹配進入as_view方法
def view(request, *args, **kwargs):
return self.dispatch(request, *args, **kwargs) # 返回dispath方法
return view
def dispatch(self, request, *args, **kwargs): # dispath方法是drf的關(guān)鍵,dispath方法會通過反射,通過請求的方法,分發(fā)到各個視圖類的方法中
pass
3、django的請求周期
因此根據(jù)CBV和FBVdjango的生命周期可以又兩類
FBV:請求通過uwsgi網(wǎng)關(guān),中間件,然后進入路由匹配,進入視圖函數(shù),連接數(shù)據(jù)庫ORM操作,模板渲染,返回經(jīng)過中間件,最終交給瀏覽器response字符串。
CBV:請求通過uwsgi網(wǎng)關(guān),中間件,然后進入路由匹配,這里就與FBV有區(qū)別了,因為不再是試圖函數(shù)而是視圖類,說的詳細一點,先經(jīng)過父類View的dispath方法,進行請求方法的判斷,在分發(fā)到視圖類的方法,連接數(shù)據(jù)庫ORM操作,模板渲染,返回經(jīng)過中間件,最終交給瀏覽器response字符串。
而再drf中主要使用CBV,生命周期就變成了如下
請求通過uwsgi網(wǎng)關(guān),中間件,然后進入路由匹配,這里就有區(qū)別了,先經(jīng)過drf中APIView類中的dispath方法(這里假定視圖類沒有重寫APIView中的dispath方法),在dispath中對request請求進行封裝,反射回到視圖類,連接數(shù)據(jù)庫ORM操作,模板渲染,返回經(jīng)過中間件,最終交給瀏覽器響應(yīng)字符串。
4、面向?qū)ο?/strong>
說到面向?qū)ο缶褪侨齻€特性,封裝,多態(tài),繼承。
<1>、子類重寫父類方法
我們在繼承父類的時候往往會重寫父類中的方法,例如
class A:
def get_name(self):
return self.name
def return_name(self):
if hasattr(self, 'name'):
return 'name: ' + getattr(self, 'name', None)
class B(A):
name = "b"
def get_name(self):
return self.name
b = B()
b.get_name() # 輸出B
b.return_name() # 輸出name: B,這里由于B類中沒有實現(xiàn)return_name方法,實例化B得到b之后,會調(diào)用父類A中的return_name方法,hasattr方法會查找類中是否有name屬性,這里雖然在類A中沒有,會向下查找B類中是否有name屬性,然后返回'name: ' + getattr(self, 'name', None) ,也就是name:b
這是簡單的子類方法重寫父類中的方法,我們再使用drf的認證,權(quán)限等組件是會經(jīng)常對父類中的方法重寫,從而細粒度的實現(xiàn)自己的功能。
請注意:事情往往不是絕對的,如果像重寫python內(nèi)置的基本數(shù)據(jù)類型,如字典,列表中的特殊方法,就會的到意想不到的結(jié)果,就是實例化的對象不再調(diào)用你重寫的方法,而是調(diào)用本來的方法。這是因為python的一些基本類型的方法是由c語言編寫的,python為了滿足速度,抄近道不會再調(diào)用你重寫的特殊方法。
<2>、mixin模式
class X(object):
def f(self):
print( 'x')
class A(X):
def f(self):
print('a')
def extral(self):
print('extral a')
class B(X):
def f(self):
print('b')
def extral(self):
print( 'extral b')
class C(A, B, X):
def f(self):
super(C, self).f()
print('c')
print(C.mro())
c = C()
c.f()
c.extral()
這樣做也可以輸出結(jié)果
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class 'object'>] # 繼承的順序是 A-->B-->X-->object 這了的object在python3中是一切類的基類,包括object類本身。
a
c
extral a # 雖然類C中沒有實現(xiàn)接口extral(),卻調(diào)用了父類A中的extral()方法
這樣的繼承雖然可以實現(xiàn)功能,但是有一個很明顯的問題,那就是在面向?qū)ο笾?,一定要指明一個類到底是什么。也就是說,如果我想構(gòu)造一個類,假如是Somthing,那么我想讓這個類實現(xiàn)會飛,會游泳,會跑,三種行為,我可以這樣做,同時繼承,鳥,魚,馬三個類,像這樣
class Bird:
def fly(self):
print('fly')
class Fish:
def swim(self):
print('swim')
class Horse:
def run(self):
print('run')
class Something(Bird, Fish, Horse):
pass
s = Something()
s.fly()
s.swim()
s.run()
輸出
fly
swim
run
可是實現(xiàn)會跑,會飛,會游泳的三種行為,但是這個類到底是什么,是魚,是馬,還是鳥,也就是說不知道Something到底是個什么類。為了解決這個問題,我們可以引用mixin模式。改寫如下
class BirdMixin:
def fly(self):
print('fly')
class FishMixin:
def swim(self):
print('swim')
class Horse:
def run(self):
print('run')
class Something(BirdMixin, FishMixin, Horse):
pass
這樣就解決了上面的問題,也許你會發(fā)現(xiàn),這其實沒有什么變化,只是在類的命名加上了以Mixin結(jié)尾,其實這是一種默認的聲明,告訴你,Something類其實是一種馬,父類是HorseHorse,繼承其他兩個類,只是為了調(diào)用他們的方法而已,這種叫做mixin模式,在drf的源碼種會用到。
例如drf中的generics 路徑為rest_framework/generics.py
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
pass
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
pass
class RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
pass
相當(dāng)于每多一次繼承,子類可調(diào)用的方法就更多了。
二、生成項目
1、生成項目
這里可以使用pycharm作為集成開發(fā)工具,創(chuàng)建django項目查看Python和第三方庫源碼很方便,使用pycharm創(chuàng)建一個django項目,然后將django rest framework作為第三方包放入django項目中

2、數(shù)據(jù)庫設(shè)計
先來看一下如果不使用drf怎么進行用戶認證,通常是用字段驗證的方式,來生成相應(yīng)的數(shù)據(jù)庫,在用戶登錄時候,對數(shù)據(jù)庫查詢,簡單的數(shù)據(jù)庫設(shè)計如下
from django.db import models
class UserInfo(models.Model):
USER_TYPE = (
(1,'普通用戶'),
(2,'VIP'),
(3,'SVIP')
)
user_type = models.IntegerField(choices=USER_TYPE, default=1)
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
class UserToken(models.Model):
user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
token = models.CharField(max_length=64)
簡單的用戶信息,每個用戶關(guān)聯(lián)一個一對一的usertoken做為驗證
然后在項目目錄下執(zhí)行生成數(shù)據(jù)庫命令
python manage.py makemigrations python manage.py migrate
3、路由系統(tǒng)
from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from api.views import AuthView
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^api/v1/auth/$', AuthView.as_view())
]
api/v1/auth/中的api分別代表接口和版本號,后面會說到
4、視圖函數(shù)
md5函數(shù)根據(jù)用戶名和用戶的訪問時間進行加密
當(dāng)用戶第一次訪問時,數(shù)據(jù)庫創(chuàng)建用戶,并將token字符串,存儲到數(shù)據(jù)庫
當(dāng)用戶下次訪問的時候,需要帶著這個字符串與數(shù)據(jù)庫比對,并返回相應(yīng)的提示信息
這里的token暫時沒有放回瀏覽器端,真正項目中可以寫入到瀏覽器cookie中
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.views import View
from api import models
def md5(user):
import hashlib
import time
# 當(dāng)前時間,相當(dāng)于生成一個隨機的字符串
ctime = str(time.time())
# token加密
m = hashlib.md5(bytes(user, encoding='utf-8'))
m.update(bytes(ctime, encoding='utf-8'))
return m.hexdigest()
class AuthView(View):
def get(self, request, *args, **kwargs):
ret = {'code': 1000, 'msg': 'success', 'name': '偷偷'}
ret = json.dumps(ret, ensure_ascii=False)
return HttpResponse(ret)
def post(self, request, *args, **kwargs):
ret = {'code': 1000, 'msg': None}
try:
user = request.POST.get('username')
pwd = request.POST.get('password')
obj = models.UserInfo.objects.filter(username=user).first()
if not obj:
# 如果用戶第一次登陸則創(chuàng)建用戶
obj = models.UserInfo.objects.create(username=user, password=pwd)
ret['code'] = 1001
ret['msg'] = '創(chuàng)建用戶成功'
# 為用戶創(chuàng)建token
token = md5(user)
# 存在就更新,不存在就創(chuàng)建
models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
ret['token'] = token
except Exception as e:
ret['code'] = 1002
ret['msg'] = '請求異常'
return JsonResponse(ret)
第一次發(fā)送請求

返回請求信息

第二次發(fā)送請求

返回請求信息

這里沒有使用drf的認證組件
三、使用Django rest framewok 認證組件
1、實例
假如用戶想獲取自己的訂單信息,發(fā)送請求之后返回訂單信息以json格式的數(shù)據(jù)返回。
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from api import models
# 這里直接表示訂單
ORDER_DICT = {
1:{
'name':'apple',
'price':15
},
2:{
'name':'狗子',
'price':100
}
}
class FirstAuthenticate(BaseAuthentication):
# 添加自己的認證邏輯,基類BaseAuthentication中有一個必須要重寫的接口
def authenticate(self, request):
pass
def authenticate_header(self, request):
pass
class MyAuthenticate(BaseAuthentication):
# 添加自己的認證邏輯,基類BaseAuthentication中有兩個必須要重寫的接口
def authenticate(self, request):
token = request._request.GET.get('token') # 獲取token參數(shù)
token_obj = models.UserToken.objects.filter(token=token).first() # 在數(shù)據(jù)庫UserToken查找是否有相應(yīng)的對象
if not token_obj: # 如果沒有,則報錯
raise exceptions.AuthenticationFailed('用戶認證失敗')
return (token_obj.user, token_obj) # 這里需要返回兩個對象,分別是UserInfo對象和UserToken對象
def authenticate_header(self, request): # 返回相應(yīng)頭信息
pass
class OrderView(APIView):
# 用戶想要獲取訂單,就要先通過身份認證、
# 這里的authentication_classes 就是用戶的認證類
authentication_classes = [FirestAuthenticate, MyAuthenticate]
def get(self, request, *args, **kwargs):
ret = {
'code': 1024,
'msg': '訂單獲取成功',
}
try:
ret['data'] = ORDER_DICT
except Exception as e:
pass
return JsonResponse(ret)
這里繼承了rest framek中的APIView,在APIView中將原生的request進行了封裝,封裝了一些用于認證,權(quán)限的類,在請求來的時候,會依次通過FirestAuthenticate, MyAuthenticate兩個類,并調(diào)用authenticate進行認證。
發(fā)送請求

返回訂單的數(shù)據(jù)

認證成功
2、源碼分析
這里推薦使用pycharm作為集成開發(fā)工具,可以ctrl+鼠標(biāo)左鍵點擊方法,或者類直接進入源碼查看
<1>、第1步
在路由匹配之后會先進入到APIView中的as_view方法中,然后進入到django的View中,

<2>、第2步
由于子類APIView已經(jīng)實現(xiàn)了dispath方法,接著返回APIView中的disapth方法

<3>、第3步
然后會發(fā)現(xiàn)drf對原生request做的操作

<4>、第4步
這里的initialize_request,主要進行封裝

<5>、第5步
而initial則會對調(diào)用封裝類中的方法,實現(xiàn)各種功能

至此可以看到request在drf中大概的流程。
3、drf認證流程
在上面第4步和第5步可以看到APIView中的兩個方法的initialize_request,initial

我們進入到initialize_request,查看authenticators=self.get_authenticators()

這里的authentication_classes,其實是一個所有認證類的集合(指的是一個可以迭代的容器對象,如list,tuple等,而不是特指set()內(nèi)置類型),

這里的api_settings其實就是django項目的全局配置文件settings.py,這說明我們可以在需要認證的視圖函數(shù)多的情況下使用全局配置使得每一個進行認證。
<1>、全局與局部配置認證類
可以直接在settings.py中添加全局配置項
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'],
}
那么如果我的個別視圖類不想認證呢?可以這樣寫
class OrderView(APIView):
# 這里沒有重寫authentication_classes屬性,則使用全局配置的authentication_classes,即在setting.py中的authentication_classes。
def get(self, request, *args, **kwargs):
pass
class CartView(APIView):
authentication_classes = [authenticate.FirstAuthenticate,] # authentication_classes中只包含F(xiàn)irstAuthenticate,則只通過他的認證
def get(self, request, *args, **kwargs):
pass
class UserInfoView(APIView):
authentication_classes = [] # authentication_classes為空,則不會進行認證
def get(self, request, *args, **kwargs):
pass
<2>、究竟如何進行認證
上面說了想要定義多個認證規(guī)則,其實就是封裝多個認證類,那么這些認證類如何進行認證呢?

這里的request類中有一個_authenticate

來分析下源碼
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators: # 找到 authentication_classes,并循環(huán)每一個認證類
try:
user_auth_tuple = authenticator.authenticate(self) # 調(diào)用認證類的authenticate方法,也就是上面我們實現(xiàn)的方法,并將返回值賦值給user_auth_tuple
except exceptions.APIException:
self._not_authenticated() # 如果出錯調(diào)用_not_authenticated,方法,下面會說到
raise
if user_auth_tuple is not None: # 如果authenticate方法的返回值不為空
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple # 這也就是為什么認證類的authenticate方法會返回兩個對象的原因
return
self._not_authenticated() # 如果沒有通過認證,則調(diào)用_not_authenticated方法
def _not_authenticated(self):
"""
Set authenticator, user & authtoken representing an unauthenticated request.
Defaults are None, AnonymousUser & None.
"""
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
_authenticate方法中調(diào)用authenticator.authenticate(self) 方法,返回給user_auth_tuple,并通過判斷user_auth_tuple是否為空,其實就像是我從瀏覽器發(fā)送請求,request中攜帶我的用戶認證信息,在進入視圖類之前,通過一次一次調(diào)用認證類來查看我攜帶的認證信息是否正確,如果正確則返回數(shù)據(jù)庫中正確的User對象。如果不通過或者沒有認證信息,則在_not_authenticated中按照匿名用戶處理。
來看一下authenticator.authenticate(self)中的authenticate(self)具體做了什么

在authenticate中可以添加具體的認證邏輯,當(dāng)然也可以在視圖類中書寫,但是drf中提供的組件,可以使得代碼耦合度更低,維護性更強,更方便。
<3>、匿名用戶認證
上面_not_authenticated的UNAUTHENTICATED_TOKEN,UNAUTHENTICATED_USER說明,也可以通過在setting.py中定義匿名用戶的認證。
只要再setting.py中添加如下
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'],
"UNAUTHENTICATED_USER": None, # 匿名,request.user = None
"UNAUTHENTICATED_TOKEN": None,# 匿名,request.auth = None
}
4、認證總結(jié)
要理解django rest framework ,就要先理解面向?qū)ο?。子類繼承父類屬性和方法,而在基類中往往以定義抽象接口的形式,強制使子類重寫抽象接口。不過抽象接口這往往是框架開發(fā)者做的,而不是我們要需要做的。實例化的對象可以調(diào)用所類的屬性和方法,其實方法也可以看作是一種屬性。子類新定義或者重寫父類的屬性,實例化的對象可以調(diào)用父類中的方法查詢到子類的屬性,就是說實例化的對象集所有父類子類于一身。子類中的方法或者屬性會覆蓋掉父類中的方法和屬性,實例化對象調(diào)用的時候不會管父類中怎么樣,所以在變量和方法命名的時候應(yīng)該注意,或者也可以使用super等操作。
而在django rest framework中,對原生request做了封裝。原本我們可以再視圖類中的進行的比如訪問限流,用戶認證,權(quán)限管理等邏輯,封裝到一個一個類中的方法中,在用戶請求進入視圖類之前,會先查找并迭代相關(guān)封裝的類,然后調(diào)用這些類的相關(guān)方法,根據(jù)返回值判斷是否滿足認證,權(quán)限等功能。如果不通過則不會進入到視圖類執(zhí)行下一步,并返回相應(yīng)的提示信息。這樣分開的好處是當(dāng)然就是最大程度的解耦,各個相關(guān)功能相互不影響,又相互關(guān)聯(lián),維護性更高。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
淺談使用Python內(nèi)置函數(shù)getattr實現(xiàn)分發(fā)模式
這篇文章主要介紹了淺談使用Python內(nèi)置函數(shù)getattr實現(xiàn)分發(fā)模式,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下2018-01-01
python 裝飾器帶參數(shù)和不帶參數(shù)步驟詳解
裝飾器是Python語言中一種特殊的語法,用于在不修改原函數(shù)代碼的情況下,為函數(shù)添加額外的功能或修改函數(shù)的行為,這篇文章主要介紹了python裝飾器帶參數(shù)和不帶參數(shù)的相關(guān)知識,需要的朋友可以參考下2024-05-05
如何在scrapy中集成selenium爬取網(wǎng)頁的方法
這篇文章主要介紹了如何在scrapy中集成selenium爬取網(wǎng)頁的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Python實現(xiàn)將多個文件的名稱或后綴名由大寫改為小寫
這篇文章主要介紹了如何基于Python語言實現(xiàn)將多個文件的名稱或后綴名由大寫字母修改為小寫,文中的示例代碼講解詳細,感興趣的可以了解下2023-09-09
Tensorflow加載Vgg預(yù)訓(xùn)練模型操作
這篇文章主要介紹了Tensorflow加載Vgg預(yù)訓(xùn)練模型操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05

