深入 DRF 原始碼:我曾只寫自訂身份驗證器,直到重新發現「內建身份驗證器」的奧秘
1. 我熱愛 DRF,卻不信任其內建身份驗證器
Django 和 DRF (Django Rest Framework) 是我開發生涯的忠實夥伴。然而,在處理身份驗證 (Authentication) 時,我總有著固執己見的一面。我主要偏好使用 JWT、API Key 或 OAuth2 等方式,因為這些在現代應用程式環境中已是標準。
然而,每當我翻閱 DRF 官方文件時,總會發現頁面主要介紹的,卻是像 Basic、Session、Token、Remote 這類看似「老舊」的身份驗證類別,而非我慣用的現代方法。我常會疑惑:「如此強大的 DRF,為何要內建這些東西?」因此,我總是繼承 BaseAuthentication,然後建立自己的自訂身份驗證器。
2. 為何要重新檢視「內建身份驗證器」的原始碼?
在開發自訂身份驗證器時,我突然產生了一個想法:
「我所建立的身份驗證器,是否真的完美融入了 Django 和 DRF 的核心哲學?」
我開始思考,除了單純地將使用者資訊放入 request.user 之外,我是否錯失了 DRF 所設計的身份驗證架構中那種「自然而然」的流暢感。因此,從今天起,我將在接下來的幾篇文章中,徹底剖析 DRF 的四種內建身份驗證器:
- BasicAuthentication (HTTP 基本身份驗證)
- SessionAuthentication (利用 Django Session)
- TokenAuthentication (簡單令牌身份驗證)
- RemoteUserAuthentication (整合外部身份驗證)
3. 在「不適感」背後尋找「哲學」
老實說,在現代服務中直接使用 BasicAuthentication 會讓人感到不自在。它看起來對安全性較為脆弱,每次都傳送使用者名稱和密碼的結構也令人不安。然而,深入分析其原始碼,你會發現其中蘊含著一套精密的設計,關於「當身份驗證失敗時,如何與客戶端溝通」。
例如,單純地拋出 403 Forbidden 與透過 authenticate_header 引導標準規範的 401 Unauthorized 之間,存在著顯著的差異。正是這些細節,造就了「Django 風格」的類別。
4. 本系列的目標:讓自訂實作「自然融入」
在本系列結束時,我期望獲得的不僅僅是知識:
- 啟發 (Inspiration): 學習內建身份驗證器為何採用這種結構,以及其背後的設計哲學。
- 移植 (Porting): 將這些哲學應用到我常用的 JWT 或 OAuth2 自訂身份驗證器中。
- 和諧 (Harmony): 最終使我建立的自訂身份驗證器,能夠在 DRF 的身份驗證系統中,像原生組件一樣自然地運作。
這不僅是實現功能的開發者,更是邁向能將框架哲學融入程式碼的開發者的第一步。DRF 身份驗證器原始碼分析系列,現在正式展開。

5. 深入原始碼:BasicAuthentication
前言說得夠多了,現在讓我們正式揭開 BasicAuthentication 的核心。
打開 DRF 的 rest_framework/authentication.py,你就能看到這個類別的真貌。它比想像中簡潔,卻蘊含著嚴謹的規則。
class BasicAuthentication(BaseAuthentication):
"""
HTTP Basic authentication against username/password.
"""
www_authenticate_realm = 'api'
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'basic':
return None
if len(auth) == 1:
msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
try:
auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
except UnicodeDecodeError:
auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
userid, password = auth_decoded.split(':', 1)
except (TypeError, ValueError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(userid, password, request)
def authenticate_credentials(self, userid, password, request=None):
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
為何非得是標頭 (Header)?
我們通常會先想到將資料放入 request.POST 或 request.data。然而,身份驗證資訊卻是「國際慣例」般地被放置在標頭中。這是為什麼呢?
因為身份驗證必須靈活應對所有 HTTP 方法 (GET, POST, PUT, DELETE 等)。GET 請求根本沒有 Body,而 DELETE 通常也缺乏請求體。如果身份驗證資訊必須放在 Body 中,那麼我們在進行查詢時,就不得不被迫使用 POST,這會導致設計變得非常奇怪。將身份驗證資訊放在標頭中,就像是「無論打開哪扇門,都請把身份證拿在手上」的約定。
冷酷的現實:安全漏洞
這種方式最大的缺點在於 Base64。它只是一種「編碼」,而非加密。任何人都能在解碼網站上,一秒鐘內獲取你的使用者名稱和密碼。
結論: 在 HTTP 環境中絕對禁止使用。它只能在 HTTPS 這種安全的通道內,並且僅限於極為有限的用途。
第一個啟發:機器間通訊 (M2M) 的高性價比安全
儘管如此,這個看似老舊的身份驗證器仍有其迷人之處,那就是在伺服器與伺服器之間進行通訊時。當建立 HMAC 或複雜的 API Key 系統會耗費過多資源,但直接傳輸資料又令人不安時,我們可以「稍微」調整這個結構:
- 自訂加密: 不使用 Base64,而是傳輸雙方伺服器皆知的對稱金鑰加密後的二進制內容。
- 簡易實作: 無需複雜的握手,僅透過
Basic <Encrypted_Data>形式,即可實現快速且安全的通訊。這就變成了一個「只有我們知道的密碼」的有效傳輸通道。
第二個啟發:失落的齒輪 authenticate_header()
最值得關注的方法莫過於 authenticate_header()。在原始碼中,你找不到任何直接呼叫它的地方。然而,它卻是 DRF 異常處理器中,產生「401 Unauthorized」的關鍵鑰匙。
當我們建立自訂身份驗證器時,常常會忽略這個方法。事實上,即使少了這個方法,身份驗證 (Authentication) 行為本身也不受任何影響。但是,這種微小的差異會讓 View 層的程式碼變得多麼混亂,我們有必要進行比較。
案例 1. 省略 authenticate_header 時 (手動方式)
如果缺少這個方法,DRF 會判斷「無法提供身份驗證方式的指引」,因此在身份驗證失敗時,它會拋出 403 Forbidden 而非 401。為了避免這種情況,我們需要在 View 中進行以下「繁瑣」的操作:
# ❌ 缺少 authenticate_header 的情況
class MyPostAPIView(APIView):
# 使用 IsAuthenticated 會直接返回 403,為了 401,我們暫時開放權限。
permission_classes = [AllowAny]
def post(self, request):
# 必須在 View 內部逐一檢查身份驗證狀態,並手動返回 401。
if not request.user or not request.user.is_authenticated:
return Response(
{"detail": "로그인이 필요합니다."},
status=status.HTTP_401_UNAUTHORIZED
)
# ... 實際邏輯開始 ...
案例 2. 實作 authenticate_header 時 (DRF 哲學的實踐)
相反地,只要在自訂身份驗證器中加入一行指引 (authenticate_header),DRF 龐大的身份驗證架構就會感知到它,並自動組裝 401 響應。
# ✅ 實作 authenticate_header 的情況
class MyPostAPIView(APIView):
# 現在 DRF 會自動拋出 401,因此我們可以宣告式地控制權限。
permission_classes = [IsAuthenticated]
def post(self, request):
# View 只需專注於「業務邏輯」。
# 身份驗證失敗的處理已在上層 (身份驗證器的 authenticate_header 方法) 完成。
...
這正是「Django 風格」的美學。它避免了將零碎的程式碼散佈在 View 中,而是將我們的程式碼完美地嵌入框架設計的齒輪之間。僅僅透過這個方法,我們就能清晰地分離「身份驗證」與「授權」的職責,並讓 View 保持輕巧。
DRF 的真正魅力:低調的讓步 (None 的藝術)
最後,讓我們再次看看原始碼的這一部分:
if not auth or auth[0].lower() != b'basic':
return None
當格式不符時,它並沒有拋出錯誤,而是返回 None,悄悄地退到後方。這種微小的處理造就了 DRF 的靈活性。正因如此,我們才能在 AUTHENTICATION_CLASSES 列表中依序放置多個身份驗證器。這是否讓你感受到開發者們的體貼,彷彿在說:「啊,這不是我的方式?那就檢查下一個身份驗證器吧!」
總結
BasicAuthentication 雖然看似過時,但其結構卻是現代身份驗證系統的基礎。特別是其異常處理和多重身份驗證支援方式,是我們在建立自訂身份驗證器時必須學習的寶貴資產。
好了,我們已經跨過了第一座山。在下一篇文章中,我們將深入探討對我們來說既熟悉又有些模糊的 SessionAuthentication。
「即使你正在使用 Session 身份驗證,卻可能不知道的那些事」,敬請期待!
目前沒有評論。