題幹與適用情境
你負責一個面向行動端與瀏覽器用戶端的 OAuth API。現有資源伺服器只驗證 Bearer access token;一次日誌外洩就可能讓攻擊者在權杖有效期間重放請求。請設計一套 DPoP(Demonstrating Proof-of-Possession)方案,說明授權伺服器、用戶端與資源伺服器的邊界,以及無法使用 DPoP 時的處理方式。
題目考察後端安全架構,不要求候選人記住某個廠商的設定畫面。RFC 9449 將 access token 綁定到用戶端公鑰,並要求每次呼叫以私鑰簽署與方法和 URI 綁定的證明;RFC 9700 則把避免弱 OAuth 流程與使用 PKCE 等措施列為安全最佳實務。
面試官考察點
強回答會先指出 Bearer 權杖的根本問題:持有字串就能使用,外洩後資源伺服器無法分辨合法用戶端與竊取者。接著給出端到端流程:用戶端產生金鑰、權杖端點驗證 DPoP proof 並綁定公鑰,資源伺服器驗證簽章、請求方法、URI、權杖雜湊與時間視窗。
面試官也會看你是否處理 jti 重放、伺服器 nonce、金鑰遺失、代理改寫 URI、更新權杖、舊用戶端相容與觀測告警。只說「替 JWT 加簽章」不足以證明理解,因為普通 JWT 的簽章並沒有證明請求者仍持有簽發時的私鑰。
回答前需要釐清的問題
用戶端與威脅模型
先確認用戶端是瀏覽器 SPA、原生行動端還是伺服器應用程式。公私鑰能否放在硬體保護區、是否允許每台裝置使用獨立金鑰,會影響金鑰生命週期。也要問攻擊者能否讀取日誌、代理、瀏覽器儲存或裝置檔案,以及是否能即時攔截請求。
資源伺服器邊界
確認 API 是否經過閘道、TLS 終止點與內部轉送。DPoP 的 htu 必須與資源伺服器驗證的 URI 語意一致;如果閘道改寫路徑,就必須定義正規化與可信轉送標頭,不能讓用戶端簽一個位址、後端卻驗證另一個位址。
可用性與遷移限制
確認是否必須支援舊 Bearer 用戶端、是否允許短暫雙軌策略、登入和更新是否由同一授權伺服器完成。若監管或高價值 API 要求強 sender constraint,就應把 DPoP 設為必要;一般低風險 API 可先透過能力協商與分階段監控遷移。
30 秒回答框架
「我會把 DPoP 當作三方協定來設計。用戶端為每次安裝產生私鑰與公鑰,在權杖請求中提交一次性 DPoP proof;授權伺服器驗證 proof 後,把 access token 的 cnf.jkt 綁定到公鑰。每次 API 請求都帶新的 DPoP JWT,資源伺服器檢查簽章、htm、htu、iat、唯一 jti、ath 與 token 的 cnf.jkt 是否一致,再用 nonce 和短時間視窗降低重放風險。私鑰遺失時撤銷綁定權杖;舊用戶端只在明確的受限範圍內暫時走 Bearer,逐步把高風險 API 切到強制 DPoP。」
分步驟深入解答
第一步:建立金鑰與授權綁定
用戶端首次執行時產生非對稱金鑰,私鑰留在裝置安全儲存,公鑰透過 DPoP proof 的 JWK 傳給授權伺服器。授權伺服器驗證 proof 的簽章、類型與時間,再把 access token 的確認資訊綁定到該公鑰的 JWK thumbprint。若使用授權碼流程,仍要啟用 RFC 9700 建議的 PKCE;DPoP 解決的是權杖持有者證明,不能取代授權碼保護。
第二步:產生每次請求的 proof
用戶端為每個請求產生新的 JWT,至少包含 jti、htm、htu 與 iat,並用綁定的私鑰簽章。存取權杖的雜湊放在 ath,讓資源伺服器確認 proof 針對的是目前權杖,而不是同一把金鑰簽出的另一個請求。用戶端將 proof 放在 DPoP 標頭,並在 Authorization 標頭使用 DPoP scheme,而非 Bearer scheme。
第三步:按固定順序驗證
資源伺服器先限制演算法與 JWK 類型,再驗證 JWT 簽章、憑證鏈或公鑰格式、時間視窗與請求方法 URI。接著計算 access token 的雜湊,與 ath 比較;讀取 token 的 cnf.jkt,與 proof 公鑰 thumbprint 比較。最後檢查 jti 是否在目前視窗出現過,再依授權範圍、受眾與資源權限執行一般授權。
第四步:處理 nonce 與重放
伺服器可以對高風險 API 發出 nonce,用戶端必須在下一份 proof 帶入該 nonce。jti 需要在視窗內去重,單機快取適合小規模部署;多實例資源伺服器應使用帶到期時間的共享快取,或接受短視窗下的重複風險並把它記錄為安全事件。拒絕回應要區分過期、簽章錯誤、thumbprint 不一致與 nonce 缺失,方便告警而不洩露金鑰細節。
第五步:設計輪替、撤銷與降級
裝置遺失、私鑰外洩或偵測到異常時,撤銷與該 thumbprint 相關的 access 與 refresh token,並要求重新授權。金鑰輪替不能只在用戶端靜默替換:新公鑰必須重新完成授權綁定。遷移期間可讓資源伺服器同時接受 Bearer 與 DPoP,但高價值寫入操作應單獨設定強制 DPoP,避免「相容」永遠成為安全預設值。
第六步:比較 mTLS 與純 Bearer
mTLS 把證明放在 TLS 用戶端憑證層,適合伺服器到伺服器且憑證基礎設施成熟的環境;DPoP 在應用層運作,更適合行動端與瀏覽器,但要維護 proof、nonce、jti 與金鑰儲存。純 Bearer 的部署最簡單,卻無法抵抗權杖字串被複製後的重放。選擇應由用戶端能力、代理拓撲、合規要求與維運成本決定,而不是把 DPoP 當作所有 API 的無條件開關。
高品質示範回答
我會先把威脅模型定清楚:日誌、代理或用戶端儲存可能外洩 access token,攻擊者隨後嘗試在有效期間呼叫 API。單純縮短到期時間只能縮短視窗,不能證明請求來自原用戶端。
用戶端為每次安裝產生金鑰並保護私鑰。拿授權碼換 token 時,我會同時要求 PKCE 和 DPoP proof;授權伺服器驗證 proof,把 access token 的 cnf.jkt 綁定到公鑰。資源伺服器收到請求後,按固定順序驗證 DPoP JWT 的簽章、htm、htu、iat、jti、ath 與 nonce,再核對 proof 公鑰與 token 的 thumbprint。任何一項不一致都回傳未授權,並把原因分類記錄到安全指標。
jti 在多實例環境放入帶 TTL 的共享去重快取。高風險寫入操作要求 nonce 和 DPoP,低風險讀取 API 在遷移期可以雙軌,但要按用戶端版本和資源等級設定截止時間。私鑰遺失時撤銷關聯 token 並重新授權;不把 Bearer fallback 放進同一條高價值路徑。最後用偽造 proof、重放舊 jti、改 URI、錯誤 token 雜湊、過期 nonce 與代理改寫做整合測試,觀察拒絕率、nonce 重試率和異常 thumbprint,確認協定真的阻斷重放。
常見錯誤
- 錯誤表現: 只給 access token 做 JWT 簽章。→ 失敗原因: 簽章證明 token 來自授權伺服器,卻沒有證明呼叫者持有用戶端私鑰。→ 修正方法: 綁定
cnf.jkt,並在每次請求驗證新的 DPoP proof。 - 錯誤表現: 重複使用同一份 DPoP proof。→ 失敗原因:
jti和時間視窗無法區分重放,攻擊者可以複製完整請求。→ 修正方法: 每次請求產生新的jti,伺服器在視窗內去重,必要時使用 nonce。 - 錯誤表現: 只在閘道驗證 DPoP,內部服務繼續相信未綁定的使用者標頭。→ 失敗原因: 轉送鏈路可能遺失方法、URI 或 token 綁定,後端重新接受偽造身分。→ 修正方法: 在可信邊界傳遞正規化驗證結果,並明確誰能設定該結果。
- 錯誤表現: 任何驗證失敗都自動回退 Bearer。→ 失敗原因: 攻擊者可以故意觸發 DPoP 錯誤,降級到最弱路徑。→ 修正方法: 只對明確登記的舊用戶端和低風險資源開放限時 fallback。
追問及應對
追問一:多地區部署如何做 jti 去重?
按資源風險選擇一致性。高價值寫入操作把 jti 去重放在跨地區可用的共享儲存,並接受一次額外往返;低風險讀取操作可以使用地區快取和短視窗,重複時記錄異常但不犧牲整體可用性。無論選擇哪種,都要把視窗、故障時行為和重複率寫入 SLO。
追問二:瀏覽器中的私鑰被 XSS 讀走怎麼辦?
DPoP 不能修復同一執行環境已被完全控制的用戶端。優先使用不可匯出的金鑰、嚴格內容安全策略、短生命週期 token 和刷新輪替;偵測到 thumbprint 異常時撤銷綁定。面試中應明確保護目標是「權杖被複製但私鑰未被複製」的重放場景,而非承諾抵禦所有端點接管。
追問三:代理會改變 URI,htu 怎麼驗證?
先定義外部規範 URI 與內部路由的映射,只有受信代理能提供經過認證的原始 scheme、host 與 path。資源伺服器用同一正規化演算法產生待比較 URI,拒絕用戶端可控的未認證轉送標頭。若無法建立可信映射,寧可在邊界終止 DPoP 並簽發內部短期身分,也不要猜測原始 URI。
追問四:舊用戶端只能發 Bearer,可以永久相容嗎?
不能把永久相容當成安全設計。按用戶端版本、使用者風險和資源類型設定遷移期限;先對高價值寫入 API 強制 DPoP,向舊用戶端回傳可操作的升級錯誤,並監控剩餘流量。只有風險評估證明必要且有額外的裝置綁定或速率限制時,才保留受限 Bearer 路徑。