題目與適用場景
一個多租戶 B2B 平台對外提供伺服器端 API。每個客戶可為不同整合建立多個 Key。系統需要隔離正式與測試環境,支援權限範圍、單一 Key 與租戶層級限額、選擇性到期時間、不中斷輪替,以及憑證外洩後的快速撤銷。
請設計簽發、儲存、驗證、授權、輪替、撤銷與稽核流程,並說明哪些場景不適合使用 API Key。呼叫端是能保護祕密的可信任伺服器;瀏覽器與行動應用程式不在這種憑證模型內。
設計必須守住五項不變條件:
- 完整 secret 只顯示一次,絕不以明文儲存或寫入日誌。
- 有效 Key 能識別機器主體,但不會自動授權所有操作。
- 一個 Key 只屬於一個租戶與一個環境。
- 撤銷要在明確且可測試的傳播時限內生效。
- 輪替期間新舊 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,例如 aklive7F3KQ2.m8…Vw。完整值只回傳一次。資料庫儲存 public ID、租戶、環境、scope、狀態、到期時間與帶金鑰的 verifier,不儲存明文 secret。
每個 TLS 請求抵達後,閘道從請求標頭取出 Key,依 public ID 查詢紀錄,重新計算 verifier,以常數時間比較,再檢查狀態與到期時間。驗證通過後建立機器主體上下文。端點 scope 與租戶資源歸屬必須分開檢查。速率限制同時套用於 Key 與租戶,稽核日誌只記錄 public ID。
例行輪替時建立第二個 Key,部署後觀察兩個 ID 的使用情況,再撤銷舊 Key。外洩時不留寬限期,立即撤銷、調查日誌並簽發替代 Key。快取必須在聲明的時限內失效。公開用戶端、使用者委派與高價值操作應改用更強或短期憑證。」
分步深入設計
第一步:建立身分與 Key 紀錄模型
把每個 Key 建模為獨立機器主體,不要讓一個租戶的所有整合共用同一 secret。一筆實用紀錄是:
ApiKey(
key_id, tenant_id, environment, name, verifier,
verifier_version, scopes, status, expires_at,
created_at, created_by, last_used_at
)keyid 可以公開並用於查詢;name 協助管理者區分「帳單匯出」與「倉庫同步」。status 至少支援 active 與 revoked,到期時間獨立判斷。createdby 與近似的 lastusedat 改善歸屬追蹤。不要在每個請求同步更新 lastusedat,否則會製造寫入熱點;分鐘層級精度足夠時,可非同步彙總或取樣更新。
scope 表示 invoices:read 這類粗粒度能力,不能取代資源授權。Key 通過驗證後,請求發票 123 的查詢仍須受驗證上下文中的 tenant_id 限制。呼叫端提交的 tenant 欄位永遠不能作為權限依據。
第二步:產生一次、顯示一次、只存 verifier
使用密碼學安全的隨機數產生器建立 32 位元組隨機 secret。這是本設計的具體選擇,不是所有協定都必須採用的通用標準。將它編碼成適合傳輸的字元,並與容易識別的前綴、public ID 組合:
ak_live_7F3KQ2.m8...opaque-secret...Vw前綴讓支援工具在不接觸 secret 的情況下識別憑證類型與環境。只在建立成功回應中傳回完整 Key,並清楚提示無法復原;遺失後只能建立替代 Key。
資料庫儲存 HMAC-SHA-256(serverpepper, completesecret) 作為 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 的安全原因碼。
驗證成功後建立包含 keyid、tenantid、環境與 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 狀態時應採失敗關閉。少數低風險讀取可明確選擇暫時使用已驗證的短期舊快取,但這會違反嚴格的即時撤銷,不能悄悄設為預設行為。
第六步:將輪替與撤銷設計成不同流程
例行輪替需要重疊期:
- 建立權限不高於舊 Key 的新 Key。
- 透過客戶的祕密管理流程交付。
- 部署並進行小流量驗證。
- 依 public key ID 觀察請求,直到舊 Key 不再使用。
- 撤銷舊 Key,並確認沒有流量繼續依賴它。
不要原地修改舊 secret。兩個獨立 ID 才能保留歸屬、小流量驗證與遷移期間的回復能力。到期時間可限制最長壽命,但缺少採用狀況觀測的強制到期會造成可避免的故障。
疑似外洩的順序不同:不留寬限期,先撤銷並清除快取,再確認受影響的租戶、scope、路由與時間範圍,檢查稽核證據,簽發最小權限替代 Key,並修復外洩來源。立即刪除紀錄可能失去事件調查證據;應依政策保留不含 secret 的中繼資料。
第七步:知道 API Key 何時不足
API Key 是 bearer 憑證,持有者即可使用。它本身不能證明呼叫端仍在預期工作負載上執行,無法表達終端使用者同意,也不能防止重播。不要把 secret Key 嵌入瀏覽器 JavaScript、行動應用程式二進位檔、範例程式碼、容器映像或程式碼儲存庫。
雲端工作負載可用時優先採用短期工作負載身分;使用者委派存取採用範圍明確且會到期的授權協定;特別敏感的服務間呼叫可考慮雙向 TLS 或請求簽章,讓複製一筆資料庫值不足以冒用。選擇應服從威脅模型;對每個 API 同時堆疊所有機制只會增加維運複雜度。
第八步:測試安全、生命週期與失敗模式
至少涵蓋以下對抗矩陣:
- 錯誤前綴、未知 ID、錯誤 secret,以及 verifier 的常數時間處理;
- 到期、已撤銷、測試 Key 呼叫正式環境與 scope 不足;
- 驗證成功後嘗試跨租戶存取資源;
- 代理、應用程式、追蹤、錯誤與稽核輸出中的 secret 遮罩;
- 輪替重疊、舊 Key 使用觀測、到期與緊急撤銷;
- 舊快取、失效訊息遺失、閘道重啟與 Key 儲存故障;
- 單一 Key 與租戶限額,包括同一租戶使用多個 Key;
- 同時建立、請求處理途中撤銷,以及重複管理請求。
還要掃描程式碼儲存庫與部署設定中的可識別 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。
lastusedat 是否必須精確?
通常不必。每個請求都更新同一列會增加寫入負載與競爭。可以傳送取樣或彙總使用事件並定期更新。安全稽核仍可在請求層追加紀錄,管理介面則明確標註 lastusedat 是近似值。
撤銷與正在處理的請求競爭時怎麼辦?
要明確定義邊界。驗證層可保證傳播完成後開始的請求遭拒。敏感操作可在提交前重新檢查授權,或把 Key 狀態綁定至交易政策。撤銷無法回溯已提交的操作,因此事件應變必須調查該時間範圍。
Key 是否應該自動到期?
到期能限制無限期暴露,但不能取代輪替與撤銷。平台應提前通知擁有者、顯示舊 Key 使用情況、允許安全重疊,並在期限後拒絕。最長壽命取決於風險,以及是否有更好的短期憑證。
IP 允許清單能解決 Key 遭竊嗎?
它可作為固定出口位址客戶的額外限制,但不能取代 secret 驗證。網路變更或共用代理也可能造成故障或錯誤安全感。把它當成一層訊號或政策,不要當作身分根基。
建立 Key 的回應應包含什麼?
只在這次回應傳回完整 Key,同時傳回 public ID 或前綴、名稱、環境、scopes、到期時間與建立中繼資料。回應應禁止快取,絕不傳回 verifier 或 pepper。後續清單 API 只傳回公開識別碼與中繼資料。