1. 「已登入卻認不出我?」(問題的起點)

OAuth2、JWT、Session 認證… 認證方式多種多樣,對於大多數情況而言已足夠。我的經驗亦是如此。

  • 為自製的 Email 客戶端或 ChatGPT 的 MyGPT 加上 OAuth2 時,彷彿體驗到真正的使用者體驗
  • 完全由 Django 伺服器提供的 Web App,Session 認證 是最佳選擇!
  • 前後端分離的架構下,JWT 最為乾淨利落

但是,這些組合在某一刻會同時失效。關鍵在於 非同步工作 (Celery)。使用者點擊按鈕時,後端不直接處理,而是將任務交給遠端的 AI 計算伺服器或 Worker 處理。此時 Worker 會說:

"嗯… 收到請求了,但這是替誰執行的?request.user 不存在呢。"

機器人 Worker 手持 API Key 送信的圖示

2. 問題在「後端 ↔ 後端」+「非同步 Worker (Celery)」

我最終決定導入 API Key,根本原因在於 後端服務之間的通訊Celery Worker 介入 的架構。

  1. 使用者發送 Web 請求
  2. 後端將「任務」放入佇列
  3. Celery Worker 消費佇列,向某個後端/計算伺服器發送非同步請求

在這個流程中,最棘手的問題是:

  • Worker 沒有 request.user
  • 也沒有 Session(因為不是瀏覽器)
  • JWT 更是令人尷尬(誰在何處、如何管理與傳遞 token)
  • OAuth2 完全依賴「使用者互動」的流程,根本不可行

當 JWT 與 Session 失效時,唯一剩下的問題歸結為:

「計算伺服器如何得知這個工作屬於哪個客戶(租戶/使用者)?」

3. 在 Worker 世界裡,先要「識別」再談「認證」

Web 請求的流程是「認證=登入」且「登入=使用者」的關係自然相連。 但 Worker 不是人,而是自動利用 CPU 執行任務的應用程式,因而在「認證」之前,必須先「識別」:

  • 任務必須以 A 客戶的資料執行
  • 結果必須存回 A 客戶的資源
  • 計費/權限/配額也必須以 A 客戶為基礎扣除

如果硬要把 JWT 或 Session 塞進來,會產生 token 的發行、保存、傳遞、過期、重新發行等繁雜設計,甚至會產生「即使是我們自己的客戶,但他並非透過瀏覽器操作,後端卻要去取得 JWT?」的強烈違和感。這種想法我隨即棄用。

4. 解決方案:API Key 在此環節既簡單又有力

於是我採用了 API Key,問題便瞬間迎刃而解。

  • Worker 在內部請求時,只需在 Header 中加上一行即可同時完成認證與識別
  • 伺服器在收到這把鍵後,即可立即 映射到對應的使用者/客戶
  • 鍵的撤銷或輪換也變得非常清晰

例如,伺服器向計算伺服器發送非同步請求的樣子如下:

POST /v1/ai/jobs
Authorization: Api-Key <KEY>
Content-Type: application/json

{ "job_id": "...", "payload": {...} }

Worker 不需要 request.user,只要發出上述請求,接收端即可依照說明使用 API Key 來辨識使用者。

5. 關鍵改進:將 API Key 與 USER 直接綁定,運維更輕鬆

我對此特別滿意。

傳統的 rest_framework_api_key 僅提供鍵本身,而我的需求則是實現 「鍵 ↔ 使用者(客戶) 的結合」。因此我繼承 AbstractAPIKey,建立 CustomAPIKey,並以 外鍵 (FK) 連結到 AUTH_USER 模型。

結果相當令人滿意。

from django.conf import settings
from rest_framework_api_key.models import AbstractAPIKey
from django.db import models

class CustomAPIKey(AbstractAPIKey):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="api_keys"
    )
    is_test = models.BooleanField(default=False)  # 區分測試/正式鍵

如此一來,除了「認證」之外,還能開啟一系列 運營功能

6. 為每位使用者自動發放鍵帶來的運營收益

在使用者註冊時自動為其生成一把鍵,使得以下工作變得非常便利。

1) 易於管理有效性

  • 可直接查詢、停用或刪除特定使用者的鍵
  • 使用者刪除時,外鍵級聯 (FK cascade) 會自動清除相關鍵

2) 鍵輪換更簡單

  • 發現鍵可能外洩時,只需發行新鍵、廢除舊鍵即可
  • 支援多把鍵同時存在,以實現 無停機替換

3) 計費/配額/權限以使用者為單位

  • 無需再額外推斷「這把 API Key 屬於誰」
  • 可直接以使用者為基礎套用計費與限制

4) 同一使用者可擁有多把鍵

is_test 旗標在此派上用場。因為 API Key 是透過 外鍵 (FK) 關聯,而非 OneToOne,單一使用者可以根據不同用途分配多把鍵。

  • 同時持有 測試環境鍵正式環境鍵
  • 開發與運營流程能輕鬆分離
  • 日誌與監控也能區分「測試流量」與「真實流量」

7. 認證方式無關優劣,而是「情境武器」

總結我的「情境最佳組合」如下:

  • OAuth2:適合外部服務/客戶端整合,需要使用者同意的流程
  • Session 認證:單一 Django 網站開發速度最快、最簡潔
  • JWT:前後端分離、行動端、SPA 等多端平衡的最佳選擇
  • API Key:後端 ↔ 後端、自動化、Worker、批次等「非使用者請求」情境最為便利

尤其在 Celery Worker 介入時,試圖用「登入基礎認證」統一全局,只會增加複雜度。此時 API Key 便成為最簡潔的出路。

8. 結語

人(瀏覽器/APP)自然使用 Session/JWT/OAuth2 處理認證。 但 Worker 不是人,而是程式,它必須能夠「辨識」這是哪個客戶的工作。

我轉向 API Key 並非因為安全議題,而是因為在那個環節它是最簡單、最直接的解法。將鍵與使用者綁定後,鍵的管理不再僅是工具,而是 運營槓桿

讀者有在使用 API Key 的經驗嗎?本文分享的做法僅是一種便利的範例,希望能為你帶來靈感。


相關文章