前言
如果我們不用使用drf
那套認證規則,我們想自定義認證類,那麼我們首先要知道,drf
本身是如何定義認證規則的,也就是要檢視它的原始碼是如何寫的
原始碼分析
原始碼的入口在APIView.py
檔案下的dispatch
方法下的self.initial
方法中的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.
"""
request.user
返回了一個request
的user
方法,request
代表的是drf
的Request
,所以我們進入drf
的Request
類中查詢user
方法屬性,原始碼如下:
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
上述程式碼的意思是:返回與當前請求關聯的使用者,由提供給請求的身份驗證類進行身份驗證。如果沒有使用者,我們需要通過_authenticate
方法驗證,我們檢視下它的原始碼
def _authenticate(self):
"""
嘗試依次使用每個身份驗證例項對請求進行身份驗證。
"""
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.user, self.auth = user_auth_tuple
return
self._not_authenticated()
我們可以看到self.authenticators
驗證器其實是呼叫父類APIView
的authenticators
,APIView
的authenticators
在原始碼initialize_request
方法下的get_authenticators
,我們檢視原始碼
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]
再點選authentication_classes
檢視
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
我們就知道了drf
預設的認證器在settings
檔案下的DEFAULT_AUTHENTICATION_CLASSES
類下面
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
我們發現drf
預設有2個認證類一個基礎的認證,另一個session
認證,這兩個認證類都繼承自BaseAuthentication
,我們來看下原始碼
class BaseAuthentication:
"""
所有的認證類都繼承自BaseAuthentication.
"""
def authenticate(self, request):
"""
認證請求返回一個二元組(user, token),並且此方法必須重寫,否則丟擲異常
"""
raise NotImplementedError(".authenticate() must be overridden.")
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
pass
接下來我們看下BasicAuthentication
如何寫的,後續我們依葫蘆畫瓢
class BasicAuthentication(BaseAuthentication):
"""
針對使用者名稱密碼的 HTTP 基本身份驗證
"""
www_authenticate_realm = 'api'
def authenticate(self, request):
"""
如果使用 HTTP 基本身份驗證提供了正確的使用者名稱和密碼,則返回“User”。否則返回“None”。
"""
# 獲取請求頭中`HTTP_AUTHORIZATION`,並進行分割
auth = get_authorization_header(request).split()
# 如果沒有auth或者auth的第一個索引值的小寫不等於basic,則返回None
if not auth or auth[0].lower() != b'basic':
return None
# auth列表的長度必須等於2,格式['basic', 'abc.xyz.123']
# 如果auth的長度等於1,則丟擲異常
if len(auth) == 1:
msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
# 如果長度大於2,也丟擲異常
elif len(auth) > 2:
msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
try:
# auth[1]解碼格式為utf-8
auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
except UnicodeDecodeError:
auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
auth_parts = auth_decoded.partition(':')
except (TypeError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
userid, password = auth_parts[0], auth_parts[2]
return self.authenticate_credentials(userid, password, request)
def authenticate_credentials(self, userid, password, request=None):
"""
Authenticate the userid and password against username and password
with optional request for context.
"""
credentials = {
get_user_model().USERNAME_FIELD: userid,
'password': password
}
user = authenticate(request=request, **credentials)
if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
if not user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (user, None)
def authenticate_header(self, request):
return 'Basic realm="%s"' % self.www_authenticate_realm
自定義認證類
- 建立繼承BaseAuthentication的認證類
- 實現authenticate方法
- 實現體根據認證規則 確定 遊客 正常使用者 非法使用者
- 進行全域性或區域性配置(一般採用全域性配置)
認證規則
- 沒有認證資訊,返回
None
(遊客) - 有認證資訊認證失敗,拋異常(非法使用者)
- 有認證資訊認證成功,返回使用者和認證資訊的元組(合法使用者)
我們建立一個資料夾authentications
,寫入如下程式碼
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from api.models import User
class MyAuthentications(BaseAuthentication):
def authenticate(self, request):
# 前臺在請求頭攜帶認證資訊
# 且預設規範用Authorization欄位攜帶認證資訊
# 後臺固定在請求物件的META欄位中的HTTP_AUTHORIZATION獲取
auth = request.META.get('HTTP_AUTHORIZATION', None)
# 處理遊客
if auth is None:
return None
auth_list = auth.split()
if not len(auth_list) == 2 and auth_list[0].lower() == "auth":
raise AuthenticationFailed("認證資訊有誤,非法使用者")
# 合法的使用者還需要從auth_list[1]中解析出來
# 注:假設一種情況,資訊為xx.yy.zz,就可以解析出admin使用者:實際開發,該邏輯一定是校驗使用者的正常邏輯
if auth_list[1] != 'xx.yy.zz': # 校驗失敗
raise AuthenticationFailed("使用者校驗失敗,非法使用者")
user = User.objects.filter(username='jkc').first()
print(user)
if not user:
raise AuthenticationFailed("使用者資料有誤,非法使用者")
return user, None
然後在settings.py
中配置全域性的自定義認證類
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'api.authentications.MyAuthentications'
],
}
最後寫入檢視函式
class TestView(APIView):
def get(self, request, *args, **kwargs):
return APIResponse(data_msg="drf get ok")
然後我們訪問檢視,在headers
中不傳Authorization
代表遊客,遊客可以訪問成功
{
"statusCode": 0,
"message": "drf get ok"
}
接著我們在請求頭中只傳auth
訪問檢視會丟擲異常資訊
{
"detail": "認證資訊有誤,非法使用者"
}
然後我們在請求頭中傳入錯誤的認證,auth 111
訪問檢視會丟擲異常資訊
{
"detail": "使用者校驗失敗,非法使用者"
}
最後我們在請求頭中傳入正確的認證,auth xx.yy.zz
,這次會得到正確的返回結果
{
"statusCode": 0,
"message": "drf get ok"
}
以上的測試,就代表我們自定義的認證類起作用了