具代表性的面試主題

後端面試:如何設計安全的 API Key 驗證?

後端困難
OfferCC 編輯團隊發佈 更新

題幹

一個多租戶 B2B 平台對外提供伺服器端 API。客戶需要隔離的正式與測試 Key、權限範圍、限額、到期時間、不中斷輪替,以及外洩後的快速撤銷。請設計 API Key 驗證系統並說明其安全邊界。

題目與適用場景

一個多租戶 B2B 平台對外提供伺服器端 API。每個客戶可為不同整合建立多個 Key。系統需要隔離正式與測試環境,支援權限範圍、單一 Key 與租戶層級限額、選擇性到期時間、不中斷輪替,以及憑證外洩後的快速撤銷。

請設計簽發、儲存、驗證、授權、輪替、撤銷與稽核流程,並說明哪些場景不適合使用 API Key。呼叫端是能保護祕密的可信任伺服器;瀏覽器與行動應用程式不在這種憑證模型內。

設計必須守住五項不變條件:

  1. 完整 secret 只顯示一次,絕不以明文儲存或寫入日誌。
  2. 有效 Key 能識別機器主體,但不會自動授權所有操作。
  3. 一個 Key 只屬於一個租戶與一個環境。
  4. 撤銷要在明確且可測試的傳播時限內生效。
  5. 輪替期間新舊 Key 可重疊使用,同時仍能識別每次請求實際使用的憑證。

面試官在評估什麼

第一個訊號是候選人能否區分驗證與授權。驗證 secret 只能確認請求來自哪個 API Key 主體;服務仍須檢查 scope、端點政策、資源歸屬與租戶隔離。

第二個訊號是祕密處理。好的回答會用密碼學安全的隨機數產生器建立不透明的高熵 secret,只顯示一次,只儲存 verifier,在所有流程遮罩,並將伺服器端 pepper 放在專用祕密管理系統。

第三個訊號是生命週期設計。Key 需要名稱、環境、狀態、到期時間、scope、歸屬、輪替與撤銷。把 Key 當成資料庫裡一段永久字串,會導致多人共用與更換中斷。

第四個訊號是維運推理。快取、速率限制、日誌、事件應變與可用性都會改變安全邊界。如果邊緣節點還能用十分鐘前的快取接受已撤銷 Key,就不能宣稱「立即撤銷」。

最後,候選人應主動排除不可信任用戶端與足夠敏感的操作。寫進前端程式碼的靜態 bearer secret 可以被使用者或攻擊者取出。高價值或使用者委派場景可能需要短期工作負載憑證、OAuth、雙向 TLS、請求簽章或加強驗證。

回答前要釐清的問題

  • 誰持有憑證? 後端服務可以保護祕密;瀏覽器、行動應用程式、桌面二進位檔與公開儲存庫無法保證祕密不被取出。
  • Key 代表誰? 明確它代表租戶整合、內部工作負載或自然人。本題把它定義為租戶擁有的機器主體,不是使用者工作階段。
  • 操作有多敏感? 唯讀分析與資金操作不應採用完全相同的控制。
  • 撤銷與可用性目標是什麼? 設定可量測的傳播時限,並決定主要 Key 儲存無法使用時要如何驗證。
  • 環境如何隔離? 測試與正式 Key 要有不同前綴、資料邊界、權限與限額。
  • 權限粒度多細? 使用粗粒度 scope 加資源層級授權;除非產品確實需要,不要先建立無邊界的自訂政策語言。
  • 每個租戶能建立多少 Key? 數量上限可控制 Key 蔓延,也能防止客戶用無限建立 Key 繞過單一 Key 限額。
  • 稽核與法遵有哪些要求? 保留期限、建立者、最近使用、核准與緊急存取都可能受到規範。

30 秒回答框架

「我會為每個 Key 分配可查詢的 public ID 與不透明隨機 secret,例如 ak_live_7F3KQ2.m8…Vw。完整值只回傳一次。資料庫儲存 public ID、租戶、環境、scope、狀態、到期時間與帶金鑰的 verifier,不儲存明文 secret。

每個 TLS 請求抵達後,閘道從請求標頭取出 Key,依 public ID 查詢紀錄,重新計算 verifier,以常數時間比較,再檢查狀態與到期時間。驗證通過後建立機器主體上下文。端點 scope 與租戶資源歸屬必須分開檢查。速率限制同時套用於 Key 與租戶,稽核日誌只記錄 public ID。

例行輪替時建立第二個 Key,部署後觀察兩個 ID 的使用情況,再撤銷舊 Key。外洩時不留寬限期,立即撤銷、調查日誌並簽發替代 Key。快取必須在聲明的時限內失效。公開用戶端、使用者委派與高價值操作應改用更強或短期憑證。」

分步深入設計

第一步:建立身分與 Key 紀錄模型

把每個 Key 建模為獨立機器主體,不要讓一個租戶的所有整合共用同一 secret。一筆實用紀錄是:

text
ApiKey(
  key_id, tenant_id, environment, name, verifier,
  verifier_version, scopes, status, expires_at,
  created_at, created_by, last_used_at
)

key_id 可以公開並用於查詢;name 協助管理者區分「帳單匯出」與「倉庫同步」。status 至少支援 active 與 revoked,到期時間獨立判斷。created_by 與近似的 last_used_at 改善歸屬追蹤。不要在每個請求同步更新 last_used_at,否則會製造寫入熱點;分鐘層級精度足夠時,可非同步彙總或取樣更新。

scope 表示 invoices:read 這類粗粒度能力,不能取代資源授權。Key 通過驗證後,請求發票 123 的查詢仍須受驗證上下文中的 tenant_id 限制。呼叫端提交的 tenant 欄位永遠不能作為權限依據。

第二步:產生一次、顯示一次、只存 verifier

使用密碼學安全的隨機數產生器建立 32 位元組隨機 secret。這是本設計的具體選擇,不是所有協定都必須採用的通用標準。將它編碼成適合傳輸的字元,並與容易識別的前綴、public ID 組合:

text
ak_live_7F3KQ2.m8...opaque-secret...Vw

前綴讓支援工具在不接觸 secret 的情況下識別憑證類型與環境。只在建立成功回應中傳回完整 Key,並清楚提示無法復原;遺失後只能建立替代 Key。

資料庫儲存 HMAC-SHA-256(server_pepper, complete_secret) 作為 verifier。pepper 放在祕密管理系統,與資料庫隔離。對隨機性足夠的 token,直接儲存 SHA-256 摘要也可行;帶金鑰的 verifier 能在只有資料庫外洩時增加一層保護。為 verifier 記錄版本,才能遷移演算法或 pepper。pepper 遷移需要有界的雙版本驗證或明確的 Key 重新簽發方案,不能悄悄讓所有客戶 Key 同時失效。

建立 Key 是經過驗證的管理操作。產生 secret 前要檢查租戶角色、Key 數量上限、允許的 scope、環境、到期政策與必要核准。儲存紀錄後,透過禁止快取的回應傳回 secret。應用程式、代理、鏈路追蹤、錯誤回報與支援工具都必須遮罩驗證標頭與回應內容。

第三步:驗證請求,但不擴大權限

強制使用 TLS,並只從驗證標頭或專用請求標頭接收 Key,絕不放在 URL 查詢參數。URL 常進入歷史紀錄、分析系統、代理日誌與 referrer。先拆解並驗證格式,再以 public ID 做索引查詢,及早拒絕格式錯誤的輸入。

找到候選紀錄後,重新計算 verifier 並使用常數時間比較,再檢查環境、active 狀態與到期時間。對外為未知、格式錯誤、已到期與已撤銷 Key 回傳同一種通用錯誤,避免端點成為 Key 列舉管道;內部只記錄不含 secret 的安全原因碼。

驗證成功後建立包含 key_id、tenant_id、環境與 scopes 的上下文。路由政策檢查所需 scope,資料層檢查租戶與資源歸屬。管理端點或高價值端點可以完全拒絕 API Key 主體,或要求額外控制。

第四步:用分層限額與監控限制濫用

速率限制不能證明身分,但能限制被竊或失控憑證造成的損害。對每個 Key 套用突發與持續速率限制,再加上租戶總限額。租戶層可防止客戶透過建立多個 Key 放大容量。昂貴端點還可能需要按成本計權的預算與並行上限。

日誌記錄 public key ID、租戶、路由、決策、延遲、政策允許的來源網路中繼資料與請求關聯 ID,絕不記錄完整 Key、verifier 或可重複使用的驗證標頭。針對異常地區或網路變化、失敗暴增、scope 拒絕暴增、長期閒置 Key 突然啟用,以及輪替通知後仍出現的舊 Key 流量發出警示。這些是調查訊號,不是自動認定外洩的證據。

Key 管理端點應比一般資料端點受到更嚴格保護:強式使用者驗證、Cookie 控制台的 CSRF 防護、明確授權、稽核事件、建立上限,以及依風險加入重新驗證或核准。

第五步:協調快取與快速撤銷

使用索引查詢 verifier 最簡單,也能讓資料庫提供權威決策;請求量非常高時才考慮快取。快取只依 public ID 儲存 verifier 與最少授權中繼資料,傳輸須加密,項目須有界;絕不快取呼叫端提交的 secret。

撤銷先寫入權威狀態,再向各閘道發布失效通知。若失效訊息遺失,短 TTL 是備援。本題可將目標定義為:已撤銷 Key 在五秒內遭所有閘道拒絕,並在封包遺失與節點重啟下測試。十分鐘 TTL 無法支援這個承諾。

失敗策略依風險選擇。敏感寫入無法取得足夠新的 Key 狀態時應採失敗關閉。少數低風險讀取可明確選擇暫時使用已驗證的短期舊快取,但這會違反嚴格的即時撤銷,不能悄悄設為預設行為。

第六步:將輪替與撤銷設計成不同流程

例行輪替需要重疊期:

  1. 建立權限不高於舊 Key 的新 Key。
  2. 透過客戶的祕密管理流程交付。
  3. 部署並進行小流量驗證。
  4. 依 public key ID 觀察請求,直到舊 Key 不再使用。
  5. 撤銷舊 Key,並確認沒有流量繼續依賴它。

不要原地修改舊 secret。兩個獨立 ID 才能保留歸屬、小流量驗證與遷移期間的回復能力。到期時間可限制最長壽命,但缺少採用狀況觀測的強制到期會造成可避免的故障。

疑似外洩的順序不同:不留寬限期,先撤銷並清除快取,再確認受影響的租戶、scope、路由與時間範圍,檢查稽核證據,簽發最小權限替代 Key,並修復外洩來源。立即刪除紀錄可能失去事件調查證據;應依政策保留不含 secret 的中繼資料。

第七步:知道 API Key 何時不足

API Key 是 bearer 憑證,持有者即可使用。它本身不能證明呼叫端仍在預期工作負載上執行,無法表達終端使用者同意,也不能防止重播。不要把 secret Key 嵌入瀏覽器 JavaScript、行動應用程式二進位檔、範例程式碼、容器映像或程式碼儲存庫。

雲端工作負載可用時優先採用短期工作負載身分;使用者委派存取採用範圍明確且會到期的授權協定;特別敏感的服務間呼叫可考慮雙向 TLS 或請求簽章,讓複製一筆資料庫值不足以冒用。選擇應服從威脅模型;對每個 API 同時堆疊所有機制只會增加維運複雜度。

第八步:測試安全、生命週期與失敗模式

至少涵蓋以下對抗矩陣:

  1. 錯誤前綴、未知 ID、錯誤 secret,以及 verifier 的常數時間處理;
  2. 到期、已撤銷、測試 Key 呼叫正式環境與 scope 不足;
  3. 驗證成功後嘗試跨租戶存取資源;
  4. 代理、應用程式、追蹤、錯誤與稽核輸出中的 secret 遮罩;
  5. 輪替重疊、舊 Key 使用觀測、到期與緊急撤銷;
  6. 舊快取、失效訊息遺失、閘道重啟與 Key 儲存故障;
  7. 單一 Key 與租戶限額,包括同一租戶使用多個 Key;
  8. 同時建立、請求處理途中撤銷,以及重複管理請求。

還要掃描程式碼儲存庫與部署設定中的可識別 Key 前綴。偵測器是補救手段,不代表可以把祕密提交到原始碼。用測試 Key 演練外洩應變:量測從確認撤銷到所有閘道拒絕的時間,並確認日誌保留歸屬資訊但不保留 secret。

高品質示範回答

「我會把每個 API Key 當成屬於單一租戶與單一環境的命名機器主體。簽發值由公開查詢 ID 與不透明隨機 secret 組成,只透過 TLS 回傳一次;資料庫儲存帶金鑰的 verifier 與生命週期中繼資料,所有日誌與追蹤層都遮罩完整值。

閘道依 public ID 查詢紀錄,重新計算 verifier,以常數時間比較,並檢查環境、狀態與到期時間。驗證通過後得到包含租戶與 scope 的上下文;每條路由仍檢查 scope,每次資料查詢仍檢查租戶歸屬。單一 Key 限額約束個別整合,租戶限額防止多個 Key 放大額度。

若快取驗證中繼資料,撤銷要先更新權威狀態並主動推送失效通知,再以短 TTL 備援。我會定義並實測五秒撤銷目標。例行輪替建立第二個 Key,小流量驗證後觀察兩個 public ID,再撤銷舊 Key。疑似外洩不留寬限期:立即撤銷,調查該 Key 的 scope 與活躍時間範圍,簽發權限更小的替代 Key,並修復暴露來源。

瀏覽器與行動應用程式、終端使用者身分,以及高價值操作的唯一控制都不應使用這種靜態 secret;這些場景需要委派式、短期、工作負載綁定或更強的憑證。」

常見錯誤

  • 儲存明文 Key 以便再次顯示。 復原便利會讓一次資料庫讀取直接變成憑證外洩;只顯示一次並支援替換。
  • 只儲存全域雜湊,不儲存生命週期中繼資料。 單純驗證無法回答租戶、環境、scope、到期、歸屬與撤銷問題。
  • 把 Key 放進查詢參數。 URL 常被複製與記錄;應透過 TLS 請求標頭傳遞。
  • 把 scope 當成租戶授權。 invoices:read 無法證明發票 123 屬於已驗證租戶。
  • 所有整合共用一個租戶 Key。 外洩的影響範圍更大,也無法準確歸屬請求。
  • 只做單一 Key 速率限制。 租戶可以建立或輪替多個 Key 來放大流量。
  • 快取數分鐘卻宣稱立即撤銷。 定義傳播目標、主動失效,並測試快取故障。
  • 透過覆寫 secret 完成輪替。 這會失去重疊期、歸屬、小流量驗證與清楚的回復路徑。
  • 在公開用戶端使用靜態 secret。 混淆處理無法建立可信任的祕密儲存邊界。
  • 為除錯驗證問題而記錄憑證。 只記錄 public ID 與安全原因碼,絕不記錄可重複使用的 secret。

追問與參考答案

為什麼使用 public ID 加 secret,而不是雜湊整個 Key 後掃描所有紀錄?

public ID 提供索引查詢、安全的支援識別碼與歸屬資訊,secret 才是證明。掃描所有 verifier 既慢,也容易引發危險日誌或次級索引。public ID 本來就不是祕密,因此知道它不能協助推導 secret。

API Key 的 verifier 可以使用快速雜湊嗎?

secret 有足夠密碼學隨機性時可以,因為它不像人類密碼那樣來自可猜測字典。帶金鑰的 HMAC 可在資料庫外洩而 pepper 未外洩時增加保護。人類選擇的密碼仍應使用密碼雜湊;兩者威脅模型不同。

如何輪替伺服器端 pepper?

每筆紀錄儲存 verifier 版本。在有界遷移期間,依版本選擇新舊 pepper;舊版本 Key 成功驗證時,記憶體中已有提交的 secret,可重新產生新版本 verifier。另一種做法是安排客戶重新簽發 Key。依賴舊 pepper 的 verifier 尚未遷移或到期前,不能移除舊 pepper。

last_used_at 是否必須精確?

通常不必。每個請求都更新同一列會增加寫入負載與競爭。可以傳送取樣或彙總使用事件並定期更新。安全稽核仍可在請求層追加紀錄,管理介面則明確標註 last_used_at 是近似值。

撤銷與正在處理的請求競爭時怎麼辦?

要明確定義邊界。驗證層可保證傳播完成後開始的請求遭拒。敏感操作可在提交前重新檢查授權,或把 Key 狀態綁定至交易政策。撤銷無法回溯已提交的操作,因此事件應變必須調查該時間範圍。

Key 是否應該自動到期?

到期能限制無限期暴露,但不能取代輪替與撤銷。平台應提前通知擁有者、顯示舊 Key 使用情況、允許安全重疊,並在期限後拒絕。最長壽命取決於風險,以及是否有更好的短期憑證。

IP 允許清單能解決 Key 遭竊嗎?

它可作為固定出口位址客戶的額外限制,但不能取代 secret 驗證。網路變更或共用代理也可能造成故障或錯誤安全感。把它當成一層訊號或政策,不要當作身分根基。

建立 Key 的回應應包含什麼?

只在這次回應傳回完整 Key,同時傳回 public ID 或前綴、名稱、環境、scopes、到期時間與建立中繼資料。回應應禁止快取,絕不傳回 verifier 或 pepper。後續清單 API 只傳回公開識別碼與中繼資料。

公開來源

同類題目