題幹與適用場景
驗證服務使用 RS256 簽發存取權杖。權杖最長可被接受 15 分鐘,允許 1 分鐘時鐘偏差。200 個後端服務從簽發方的 OpenID Discovery 文件讀取固定的 jwks_uri,把 JWKS 快取 10 分鐘並在本機驗簽。
請設計一套不會讓合法請求集中出現 401 的簽章金鑰輪替方案,並涵蓋以下情況:
- 正常輪替期間,新舊權杖同時到達;
- 快取中找不到權杖標頭的
kid; - 大量隨機
kid試圖觸發 JWKS 重新整理風暴; - JWKS 端點在輪替期間逾時或回傳錯誤;
- 目前私鑰疑似洩漏,需要緊急止損;
- 如何證明所有驗證方已經接受新金鑰,並安全退役舊金鑰。
15 分鐘、10 分鐘、1 分鐘和 200 個服務都是題目限制,不是通用建議值。核心考察點是後端驗證協定、快取一致性、失敗語意與安全維運,因此歸為 backend。
面試官考察點
第一,候選人能否說出正確順序:先發布新公鑰,再讓驗證方看見它,然後才用新私鑰簽發;最後等待舊權杖過期,再移除舊公鑰。 如果先切換簽發器,尚未重新整理 JWKS 的服務必然拒絕新權杖。
第二,是否理解 JWKS 是公鑰集合。RFC 7517 定義 keys 陣列,kid 只是從可信集合中選擇金鑰的識別碼。私鑰不能發布到 JWKS;kid 也不是信任證明,必須同時綁定可信簽發方、固定演算法和簽章驗證。
第三,是否能平衡快取與安全。每次請求都拉取 JWKS 會壓垮簽發方,永久快取則無法及時接受新金鑰或停止信任舊金鑰。未知 kid 可以觸發一次受控重新整理,但必須合併並行重新整理、限速並短暫快取確認不存在的識別碼。
第四,是否區分計畫輪替與私鑰洩漏。計畫輪替允許新舊公鑰重疊以保護可用性;洩漏時繼續保留舊金鑰會讓攻擊者偽造權杖。緊急流程需要明確快取失效、撤銷策略和更強的安全優先級。
第五,是否把驗簽延伸為完整驗證。驗證方應固定允許的演算法,檢查簽章、iss、aud、exp 與 nbf,並拒絕來源不可信的 jku 或任意金鑰位址,避免演算法混淆與 SSRF。
回答前需要釐清的問題
- 權杖的最長接受時間是多少? 應使用所有可能被接受的舊權杖壽命,不只看設定中的名義 TTL;還要包括時鐘偏差。
- JWKS 的快取上限是多少? 瀏覽器、CDN、代理與程序內快取可能各有一層,輪替前要取得真實的最大陳舊時間。
- 能否主動重新整理所有驗證方? 若有設定推送、版本確認或金絲雀探測,就不必只靠等待快取自然到期。
- 驗證方在 JWKS 故障時如何處理? 需要明確已知金鑰能否在有限時間內使用陳舊快取,以及未知金鑰是否嚴格拒絕。
- 是否要求立即撤銷已簽發權杖? 自包含 JWT 不能只靠換金鑰做到精確單權杖撤銷,可能需要拒絕清單、權杖版本或內省。
- 誰控制簽發方與驗證方? 同一組織內可做重新整理確認;第三方驗證方通常只能依賴公開相容視窗。
- 金鑰放在哪裡? 私鑰應在 KMS、HSM 或受限金鑰服務中產生和使用,發布面只包含公鑰。
- 成功標準是什麼? 計畫輪替要讓合法新舊權杖持續通過;緊急輪替則可能接受可控的重新登入,以盡快停止信任洩漏金鑰。
30 秒回答框架
「我把輪替拆成發布、預熱、切簽、重疊和退役五步。先產生帶全新 kid 的金鑰,把新公鑰與舊公鑰同時發布到 JWKS;等待最長 10 分鐘快取視窗,或讓 200 個驗證服務主動重新整理並回報新 JWKS 版本。確認新公鑰可用後,簽發器才改用新私鑰。
舊公鑰繼續保留到最後一個舊權杖簽發後的 15 分鐘壽命加 1 分鐘時鐘偏差結束。驗證方按可信簽發方與固定 RS256 選擇 kid,檢查簽章、iss、aud、exp 和 nbf。未知 kid 只觸發一次合併且限速的重新整理,重新整理後仍不存在就拒絕;JWKS 故障時,已知金鑰可按明確的有限陳舊策略繼續使用,未知金鑰 fail closed。
若私鑰洩漏,我會停止舊金鑰簽發、發布並切換新金鑰,同時廣播快取失效並撤銷舊金鑰簽發的權杖;不能套用正常重疊視窗。最後用按 kid 的驗簽結果、快取年齡、JWKS 拉取量和舊 kid 最後出現時間證明輪替完成。」
分步驟深入解答
第一步:固定信任根與驗證不變量
驗證服務從設定中的可信簽發方讀取 OpenID Discovery,再使用其中 HTTPS 的 jwks_uri。權杖標頭的 kid 只用於尋找這份可信 JWKS 中的候選公鑰,不能讓權杖透過 jku 指向任意位址,也不能把 kid 直接拼進檔案、資料庫或 URL 查詢。
每次驗證至少保持以下不變量:
- 只接受設定允許的
RS256,權杖不能自行協商演算法; kid必須唯一匹配可信簽發方 JWKS 中用途為簽章的公鑰;- 簽章通過後繼續檢查預期
iss、目前 API 的aud、exp和nbf; - 任一驗證失敗都拒絕整個權杖;
- JWKS 只發布公鑰材料,私鑰始終留在受控簽發邊界內。
一個輪替中的最小公鑰集合可以是:
{
"keys": [
{ "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "2026-07-a", "n": "...", "e": "AQAB" },
{ "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "2026-07-b", "n": "...", "e": "AQAB" }
]
}集合順序不表示優先級;驗證方按 kid 精確選擇。新金鑰必須使用新的、不重複使用的 kid,否則快取無法區分相同識別碼下被替換的金鑰材料。
第二步:按發布先於簽發的順序執行計畫輪替
計畫輪替可建模為以下狀態:
GENERATED
-> PUBLISHED(old + new)
-> VERIFIER_READY
-> SIGNING_WITH_NEW
-> OLD_TOKEN_DRAINED
-> OLD_KEY_RETIRED具體流程如下:
- 在受控金鑰系統中產生新金鑰
2026-07-b,簽發器尚不使用它; - 發布包含
2026-07-a與2026-07-b的 JWKS,並產生新的 ETag 或集合版本; - 等待 10 分鐘最大快取陳舊視窗,或主動重新整理 200 個驗證方並收集它們識別新
kid的確認; - 用金絲雀權杖驗證各環境的簽章、簽發方、受眾和時間宣告;
- 只有新公鑰已被驗證方接受後,簽發器才開始用
2026-07-b; - 記錄最後一個舊金鑰權杖的簽發時間
Tlastold; - 至少到
Tlastold + 15 分鐘 + 1 分鐘後,且舊kid流量符合預期,再從 JWKS 移除舊公鑰; - 繼續監控異常舊
kid,最後停用並銷毀舊私鑰材料。
「先發布、後簽發」保護新權杖,「停止舊簽發、等待舊權杖排空、再移除」保護舊權杖。快取傳播時間影響切簽的最早時刻;舊權杖接受壽命與時鐘偏差決定移除舊公鑰的最早時刻。
第三步:設計未知 kid 的單次受控重新整理
未知 kid 既可能是剛輪替的新金鑰,也可能是攻擊者建構的隨機值。驗證方不能立即拒絕所有未知值,也不能讓每個未知值無限拉取 JWKS。可使用以下邏輯:
verify(token):
header = parse_bounded_header(token)
require header.alg == "RS256"
key = trusted_cache.find(header.kid)
if key is missing:
refresh trusted_issuer_jwks once through single-flight
key = trusted_cache.find(header.kid)
if key is missing:
short_negative_cache.add(header.kid)
reject "unknown kid"
verify signature and require iss, aud, exp, nbf同一簽發方的並行 miss 應共享一個 single-flight 重新整理。重新整理受全域冷卻時間和逾時限制;在確認最新集合仍不包含該 kid 後,可對該值做短時負快取,避免隨機識別碼反覆觸發上游請求。負快取不能長到妨礙真正輪替,且 kid 的長度和格式要先受限,防止高基數記憶體攻擊。
重新整理只能存取設定綁定的 jwks_uri,使用 TLS、合理的回應大小上限、連線與讀取逾時,並支援 ETag 條件請求。攻擊者提供的 jku、x5u 或相似位址不得覆蓋這個信任根。
第四步:明確 JWKS 故障時的可用性邊界
正常請求應完全使用本機快取,不把 JWKS 端點放入每次驗證的同步路徑。若重新整理失敗:
- 快取中已有匹配
kid且金鑰仍在事先定義的有限陳舊視窗內,可以繼續驗簽; - 快取中沒有匹配
kid,必須拒絕,不能跳過簽章或嘗試所有不相關金鑰; - 超過有限陳舊視窗仍無法重新整理,應告警並按安全策略拒絕;
- 簽發方若無法證明驗證方已經取得新公鑰,就不能開始用新私鑰簽發。
有限的 stale-if-error 能吸收短時網路故障,但會延長已刪除公鑰的本機信任時間。這個視窗必須寫進安全模型,並在金鑰洩漏時用明確失效機制覆蓋。可用性不能依賴永久陳舊快取。
第五步:為私鑰洩漏建立獨立緊急流程
洩漏場景的目標是盡快停止接受攻擊者可能偽造的權杖。處理順序為:停止舊私鑰簽發,產生並發布新公鑰,強制驗證方重新整理,切換新私鑰,並讓舊金鑰立即進入撤銷狀態。此時不能為了無感體驗繼續正常重疊 16 分鐘。
只從中心 JWKS 刪除舊公鑰仍不夠,因為驗證方可能保留 10 分鐘快取。需要控制面廣播、快取版本推送、服務重啟或其他已演練的失效通道。若無法觸達第三方驗證方,只能依賴它們的快取上限,並應在風險評估中明確暴露視窗。
換金鑰也不會精確召回已經簽發的自包含權杖。若業務要求立即撤銷,可按舊 kid 或 iat 截止點維護臨時拒絕策略,縮短存取權杖壽命,或對高風險介面使用內省/工作階段狀態。緊急處理可能迫使用戶重新驗證,這是安全優先下可接受的業務影響。
第六步:用版本、指標和稽核證明完成
JWKS 回應應有可觀察的集合版本或 ETag。驗證服務回報目前版本、快取年齡、重新整理結果和識別到的 kid;簽發服務記錄目前簽章 kid,但不記錄權杖正文或私鑰材料。
關鍵指標包括:按簽發方、kid 和錯誤類型分組的驗簽結果,未知 kid 基數,JWKS 拉取次數、延遲與失敗率,single-flight 合併數,快取年齡,新 kid 金絲雀成功率,以及舊 kid 最後一次合法出現時間。若切簽後新 kid 的 unknown-key 錯誤上升,應自動停止或回復簽發切換,而不是等待使用者回報。
稽核記錄要能回答:誰在何時產生、發布、啟用和退役了哪個金鑰;當時 JWKS 版本是什麼;哪些驗證方確認就緒;舊金鑰最後簽發時間與退役依據是什麼。金鑰生命週期操作使用雙人核准和最小權限更穩妥。
第七步:涵蓋過渡態與攻擊面的測試矩陣
測試不能只驗證一枚靜態有效權杖,至少應涵蓋:
- 只發布舊公鑰時,舊權杖通過、新權杖拒絕;
- 新舊公鑰重疊時,兩類權杖都通過,且重複驗簽不重複拉取 JWKS;
- 快取只有舊公鑰時,新
kid觸發一次重新整理後通過; - 大量相同或隨機未知
kid只產生受控數量的重新整理,最終全部拒絕; - JWKS 回傳逾時、500、過大回應或非法 JSON 時,已知金鑰按策略工作,未知金鑰拒絕;
- 固定演算法、錯誤
iss、錯誤aud、過期和尚未生效的權杖全部拒絕; - 權杖帶有攻擊者
jku時,驗證方仍只存取設定的可信位址; - 舊公鑰移除後,新程序拒絕舊權杖;仍有舊快取的程序只在明示視窗內接受;
- 洩漏演練中,失效訊號能讓所有受控驗證方停止接受舊
kid; - 切簽門禁在驗證方未就緒時阻止新私鑰啟用。
驗收要同時看授權結果和 JWKS 請求次數。權杖通過但每次都存取上游不是合格實作;請求量正常但合法新權杖被拒絕同樣不合格。
高品質示範回答
「我先把 kid 定義為可信簽發方 JWKS 內的選擇器,不把它當信任來源。每個驗證服務只接受設定允許的 RS256,從固定 Discovery 的 jwks_uri 取得公鑰,按 kid 選鑰後檢查簽章、iss、aud、exp 和 nbf。JWKS 只包含公鑰,私鑰留在 KMS 或 HSM 的簽發邊界。
計畫輪替時,我先產生帶新 kid 的金鑰,把新舊公鑰一起發布並更新 ETag。接著等待題目中的 10 分鐘最大快取視窗,或主動重新整理 200 個驗證服務並收集就緒狀態,再用金絲雀權杖確認新金鑰可驗證。只有這一步通過,簽發器才切到新私鑰。
我會記錄最後一枚舊權杖的簽發時間。舊公鑰至少保留到該時刻後的 15 分鐘接受壽命加 1 分鐘時鐘偏差,並確認舊 kid 已排空後再移除。這樣切換兩邊都有明確門禁:新公鑰已傳播才簽新權杖,舊權杖失效後才撤舊公鑰。
快取 miss 時,同一簽發方只允許一個 single-flight 重新整理,帶逾時、冷卻和 ETag;重新整理後仍找不到就拒絕並短暫負快取。隨機 kid 不會放大成上游洪峰。JWKS 暫時故障時,已知金鑰只在明示的有限陳舊視窗內繼續使用,未知金鑰始終 fail closed。
如果舊私鑰洩漏,我會停止舊簽發、發布並切換新金鑰,同時廣播快取失效並撤銷舊 kid 或舊 iat 範圍的權杖。這個流程不保留正常重疊視窗,因為攻擊者可能正在簽發。最後透過 JWKS 版本、快取年齡、按 kid 的驗簽錯誤、新鑰金絲雀和舊 kid 最後出現時間證明輪替完成,並定期演練過渡態和上游故障。」
常見錯誤
- 先切換簽發器再發布公鑰 → 快取舊 JWKS 的服務無法驗證新權杖 → 先發布、確認傳播,再開始新簽發。
- 新 JWKS 只放新公鑰 → 尚未過期的舊權杖立即失效 → 在接受視窗內同時發布新舊公鑰。
- 固定等待一個憑感覺的時間 → 可能小於真實快取或權杖壽命 → 從最大快取陳舊時間、最後舊簽發時間、權杖壽命和時鐘偏差推導門禁。
- 每次請求都下載 JWKS → 簽發方故障會拖垮所有驗證並放大流量 → 本機快取、條件重新整理和有限陳舊策略。
- 每個未知 kid 都立即重新整理 → 隨機
kid可製造重新整理風暴 → single-flight、限速、負快取和輸入上限。 - 未知 kid 時嘗試所有金鑰 → 模糊金鑰選擇規則並增加攻擊面 → 重新整理一次,仍無精確匹配就拒絕。
- 信任權杖裡的 alg 或 jku → 可能產生演算法混淆或 SSRF → 演算法和 JWKS 位址都由驗證方設定固定。
- 只驗簽不檢查宣告 → 其他簽發方、受眾或過期權杖仍可能被接受 → 同時檢查
iss、aud、exp和nbf。 - 重複使用 kid 但替換金鑰材料 → 快取無法判斷同一識別碼已改變 → 每一代金鑰使用全新識別碼。
- 把刪除中心 JWKS 當成即時撤銷 → 驗證方仍可能持有舊快取 → 提供明確快取失效與權杖撤銷方案。
- 洩漏時繼續正常重疊 → 攻擊者可在整個重疊期偽造權杖 → 走獨立緊急流程並接受必要的重新驗證。
追問及應對
追問一:舊公鑰到底要保留多久?
從最後一個舊金鑰權杖的簽發時間開始,至少保留「最大接受壽命 + 時鐘偏差」。本題是 15 + 1 = 16 分鐘。若系統還允許佇列延遲、離線簽發或更長的隱含接受視窗,也要計入。移除前同時觀察舊 kid 流量,避免設定事實與執行事實不一致。
追問二:未知 kid 為什麼不能直接回傳 401?
因為合法輪替後,新權杖可能早於本程序的自然快取重新整理到達。一次受控重新整理可以消除這段視窗。重新整理必須合併並行、限速且只存取可信位址;最新集合仍無該 kid 時再拒絕,兼顧相容性和抗放大攻擊。
追問三:JWKS 端點停擺時是否應該 fail open?
不能跳過簽章驗證。快取中有已信任且仍處於明示陳舊視窗的匹配公鑰時,可繼續完成完整驗證;未知金鑰或超過視窗時拒絕。這個策略讓短時控制面故障不進入每次資料面請求,同時保持可量化的信任上限。
追問四:刪除洩漏金鑰後,為什麼舊權杖還可能有效?
驗證方可能仍持有 10 分鐘舊快取,而且自包含 JWT 沒有中心查詢。緊急流程需要主動失效快取,並按舊 kid、簽發時間或權杖版本執行臨時拒絕;高風險系統也可以使用內省或伺服器端工作階段取得更快撤銷。
追問五:如何防止輪替引發重新整理風暴?
預先發布讓多數程序在自然重新整理中先取得新鑰;主動重新整理可以分批並加入抖動。真實 miss 到來時,每個簽發方用 single-flight 合併並行請求,再配合冷卻、ETag、逾時和短負快取。監控 JWKS 請求量與 unknown-kid 基數,異常時限流而不是增加重試。
追問六:怎樣自動化切簽門禁?
為 JWKS 分配版本,驗證服務定期回報已載入版本和可識別 kid。發布控制器要求目標服務達到預設覆蓋率,並讓關鍵路徑金絲雀權杖成功後才啟用新私鑰。任何新 kid 驗證失敗率上升都暫停或回復切簽,但保留已發布的新公鑰不會破壞舊權杖。
追問七:第三方驗證方無法回報就緒怎麼辦?
必須公開穩定的重疊合約:新公鑰提前發布至少一個最大快取週期,舊公鑰保留到所有舊權杖過期,並提供合理的快取標頭。簽發方無法強制第三方重新整理,因此相容視窗、變更通知和金絲雀驗證比內部確認更重要;緊急洩漏時仍可能產生不可避免的風險視窗。