DRF內置認證組件之自定義認證系統
- 2020 年 8 月 12 日
- 筆記
- Django REST framework
自定義token認證
我們知道,在django項目中不管路由以及對應的視圖類是如何寫的,都會走到 dispatch
方法,進行路由分發,
在閱讀 APIView類中的dispatch
方法的源碼中,有個 self.initial(request, *args, **kwargs)
,可以發現認證、權限、頻率這三個默認組件都在這個方法裏面,如果我們自己沒有定義這三個組件的配置,那麼就會使用源碼中默認的一些配置。
源碼:
# Ensure that the incoming request is permitted
#實現認證
self.perform_authentication(request)
#權限判斷
self.check_permissions(request)
#控制訪問頻率
elf.check_throttles(request)
目前為止大家知道的認證機制是不是有cookie、session啊,session更安全一些,但是你會發現session的信息都存到咱們的服務器上了,如果用戶量很大的話,服務器壓力是比較大的,並且django的session存到了django_session表中,不是很好操作,但是一般的場景都是沒有啥問題的,現在生產中使用的一個叫做token機制的方式比較多,現在我們是不是就知道個csrf_token啊,其實token有很多種寫法,如何加密,你是hashlib啊還是base64啊還是hmac啊等,是不是加上過期時間啊,是不是要加上一個secret_key(客戶端與服務端協商好的一個字符串,作為雙方的認證依據),是不是要持續刷新啊(有效時間要短,不斷的更新token,如果在這麼短的時間內還是被別人拿走了token,模擬了用戶狀態,那這個基本是沒有辦法的,但是你可以在網絡或者網絡設備中加安全,存客戶的ip地址等,防黑客)等等。
大致流程圖解:
首先我們需要創建一個表,用戶表,裏面放一個token字段,其實一般我都是放到兩個表裏面,和用戶表是一個一對一關係的表,看代碼:
models.py內容如下:
################################# user表 ###############################
class User(models.Model):
user = models.CharField(max_length=32)
pwd = models.CharField(max_length=32)
type_choice=((1,"VIP"),(2,"SVIP"),(3,"SSVIP"))
user_type = models.IntegerField(choices=type_choice)
class UserToken(models.Model):
user = models.OneToOneField(to=User) #一對一到用戶表
token = models.CharField(max_length=128) #設置的長度大一些
# expire_time = models.DateTimeField() #如果做超時時間限制,可以在這裡加個字段
urls.py內容如下:
#登陸認證接口
url(r'^login/$', views.LoginView.as_view(),), #別忘了$符號結尾
views.py內容如下:每次登陸成功之後刷新token值
###################login邏輯接口#######################
#關於邏輯接口而不是提供數據的接口,我們不用ModelViewSet,而是直接寫個類,繼承APIView,然後在類裏面直接寫咱的邏輯
import uuid
import os
import json
class LoginView(APIView):
#從前後端分離的項目來講,get請求不需要寫,因為get就是個要登陸頁面的操作,vue就搞定了,所以我們這裡直接寫post請求就可以了
def post(self,request):
# 一般,請求過來之後,我們後端做出的響應,都是個字典,不僅包含錯誤信息,還有要狀態碼等,讓客戶端明白到底發生了什麼事情
# 'code'的值,1表示成功,0表示失敗,2表示其他錯誤(自己可以做更細緻的錯誤代碼昂)
res = {'code': 1, 'msg': None, 'user': None,'token':None}
print(request.data)
try:
user = request.data.get('user')
pwd = request.data.get('pwd')
# 數據庫中查詢
user_obj = models.User.objects.filter(user=user, pwd=pwd).first()
if user_obj:
res['user'] = user_obj.user
# 添加token,用到咱們usertoken表
# models.UserToken.objects.create(user=user,token='123456')
# 創建token隨機字符串,我寫了兩個方式,簡寫的昂,最好再加密一下
random_str = uuid.uuid4()
# random_str = os.urandom(16) bytes類型的16位的隨機字符串
models.UserToken.objects.update_or_create(
user=user_obj, # 查找篩選條件
defaults={ # 添加或者更新的數據
"token": random_str,
}
)
res['token'] = random_str
res['msg'] = '登陸成功'
else:
res['code'] = 0
res['msg'] = '用戶名或者密碼錯誤'
return Response(res)
except Exception as e:
res['code'] = 2
res['msg'] = str(e)
return Response(res)
通過上面的代碼我們將token返回給了用戶,那麼以後用戶不管發送什麼請求,都要帶着我給它的token值來訪問,認證token通過才行,並且更新token。
將來有些數據接口是必須要求用戶登陸之後才能獲取到數據,所以將來用戶登陸完成之後,每次再過來請求,都要帶着token來,作為身份認證的依據。
DRF內置的認證組件
在DRF的全局配置信息中,有DRF內置組件的相關配置 :
# 基於django基礎配置信息上的drf框架的配置信息
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
...
}
補充:
django項目的request對象中有一個user屬性,是一個默認的假用戶
AnonymousUser
,當用戶未登錄admin後台管理系統時,request.user = AnonymousUser
若登錄了admin後台,在django內置的認證系統中,request.user = 當前登錄用戶
DRF中內置的認證系統
'rest_framework.authentication.SessionAuthentication'
跟django中的認證系統掛鈎的,使用的也是admin後台的用戶信息
DRF內置組件依據session信息做認證,根據上面的敘述,session不適合做大型項目的認證載體,太重了!所以我們可以藉助DRF內置的認證系統進行自定義認證系統。
以下是其認證機制的部分源碼實現:
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
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是APIView封裝的新的request對象
self.user, self.auth = user_auth_tuple
return #退出了這個函數,函數就不會執行了,不會再循環了,所以如果你的第一個認證類有返回值,那麼第二個認證類就不會執行了,所以別忘了return是結束函數的意思,所以如果你有多個認證類,那麼返回值放到最後一個類裏面
from rest_framework.authentication import BaseAuthentication
# 'BaseAuthentication'這個類源碼實現中有個'authenticate'方法,我們可以重寫這個類進行自定製咱們的認證系統。
"""
def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
raise NotImplementedError(".authenticate() must be overridden.")
"""
一、基於drf提供的認證類BaseAuthentication進行自定製認證系統
1、自定義認證類MyAuthentication,繼承自BaseAuthentication類且重寫其authenticate方法
# utils.auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
if 1:
return "aliang", "fengting007"
else:
return AuthenticationFailed("認證失敗!")
2、全局配置
對自定義的認證類MyAuthentication中的認證流程進行全局配置,即在訪問每個url時都要經過自定義的認證邏輯
- 在settings.py配置文件中進行配置
# 基於django基礎配置信息上的drf框架的配置信息
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
# 自定義認證類的路徑
'four.utils.auth.MyAuthentication',
# 注釋掉drf內置的session認證系統,使用自己定製的!
# 'rest_framework.authentication.SessionAuthentication',
),
...
}
3、局部配置
即在訪問某個指定的url時,需要認證後才可以獲取到響應頁面,而不是訪問所有url都需要認證,這就要在指定的url所映射的類視圖中進行定製
from rest_framework.views import APIView
from rest_framework.response import Response
from four.utils.auth import MyAuthentication
class AuthAPIView(APIView):
# 訪問AuthAPIView類視圖所對應的url請求響應頁面時,要經過自定製的MyAuthentication類的認證邏輯!
authentication_classes = [MyAuthentication,]
def get(self, request):
print(request.user) # 'aliang'
print(request.auth) # 'fengting007'
return Response({'msg':'嗨,man!'})
二、不繼承drf提供的認證類BaseAuthentication自定製認證類
class MyAuthentication():
def authenticate_header(self,request):
pass
# authenticate方法是固定的,並且必須有個參數,這個參數是新的request對象
def authenticate(self, request):
if 1:
# 源碼中會發現,這個方法會有兩個返回值,並且這兩個返回值封裝到了新的request對象中了,request.user-->用戶名 和 request.auth-->token值,這兩個值作為認證結束後的返回結果
# 如下操作即:
# request.user = 'aliang'
# request.auth = 'fengting007'
return "aliang", "fengting007"
至於此自定義認證類的全局配置以及局部配置的方法與上面的流程是一致的。
# 局部配置示例:
from app01.serializer import BookSerializers
from four.utils.auth import MyAuthentication
class BookView(APIView):
#認證組件肯定是在get、post等方法執行之前執行的,源碼中這個組件是在dispatch的地方調用的
authentication_classes = [MyAuthentication,] #認證類可以寫多個,一個一個的順序驗證
def get(self,request):
#這樣就拿到了上面MyAuthentication類的authenticate方法的兩個返回值
print(request.user)
print(request.auth)
book_obj_list = models.Book.objects.all()
s_books = BookSerializers(book_obj_list,many=True)
return Response(s_books.data)