具代表性的面試主題

後端面試:如何設計安全的 OAuth 委派權杖交換?

後端困難
Offer.cc 編輯團隊發佈 更新

題幹

一個多租戶 SaaS 的 API 閘道代表登入使用者呼叫帳單服務。閘道收到身分平台簽發的存取權杖,但不能把原權杖直接轉發給所有下游。請設計 OAuth 權杖交換端點,說明如何避免越權、混淆代理、重放與撤銷延遲。

題目與適用場景

一個多租戶 SaaS 的 API 閘道代表登入使用者呼叫帳單服務。身分平台簽發的上游存取權杖面向閘道,帳單服務只接受自己的受眾。閘道需要在使用者請求內取得更窄的下游權杖,同時保留「誰代表誰行動」的可稽核關係。

請設計權杖交換端點、驗證規則、宣告映射、快取、錯誤處理、撤銷與監控。假設閘道是受控伺服器端,瀏覽器不直接保存下游權杖;帳單服務必須拒絕面向其他服務的權杖。

五條不變量:

  1. 交換服務只接受已驗證且允許被交換的 subject token。
  2. 新權杖的 audience、scope、租戶與資源範圍不得比上游授權更寬。
  3. 權杖中的 subject 與 actor 同時可追溯,不能把服務身分偽裝成人。
  4. 下游服務只信任自己的 issuer、受眾與簽章金鑰。
  5. 撤銷、過期、金鑰輪換與稽核邊界都有可量測的時限。

面試官在考察什麼

候選人應先區分 token exchange、token forwarding、impersonation 與 delegation。RFC 8693 定義 HTTP/JSON 的 Security Token Service;請求可以帶 subject_token,也可以帶 actor_token,回應是普通 OAuth token endpoint 回應的擴充。它不會自動賦予呼叫方更大權限。

第二個訊號是受眾約束。閘道不能把面向自己的 bearer token 原樣轉發給帳單、搜尋與匯出服務。每個下游權杖都要寫入單一 audience,並只授予本次呼叫所需 scope。

第三個訊號是代理關係。只有 subject token 表示的主體允許該 actor 代理時才能交換;may_actact 等宣告要有明確的信任來源。把客戶端提交的任意 subtenant_id 複製進新權杖屬於越權。

最後要討論失敗模式:交換端點快取了舊授權判斷、下游只驗簽不驗 audience、權杖被記錄到日誌、refresh token 被錯誤交給閘道,以及上游撤銷在下游 TTL 內仍然有效。

回答前要釐清的問題

  • 誰是 subject,誰是 actor? 本題中 subject 是登入使用者,actor 是 API 閘道;服務帳號代替使用者時是否允許要寫入策略。
  • 交換後的權杖給誰使用? 帳單服務是唯一 audience,還是允許多個資源?多資源會擴大洩露爆炸半徑。
  • scope 如何縮小? 上游有 billing:read billing:write,本次請求只需要哪一個?
  • 租戶邊界在哪裡驗證? 身分宣告、工作階段資料庫和資源資料庫誰是權威?不能信任請求本體租戶欄位。
  • 撤銷目標是什麼? 例如上游撤銷後五秒內不再簽發新權杖,現有下游權杖最多還有兩分鐘壽命。
  • 閘道是否需要 refresh token? 代理呼叫通常只需要短期 access token;把長期 refresh token 下發給閘道需另行評估。

30 秒回答框架

「我會讓閘道呼叫標準 token endpoint,以 subject_token 表示使用者、以 actor_token 表示閘道,並請求固定的帳單 audience 與最小 scope。交換服務先驗證 issuer、簽章、expnbf、客戶端認證、subject 的可代理策略與租戶狀態,再透過策略表把上游 scope 映射為允許的下游 scope。新權杖只含帳單 audience、短過期時間、租戶和 subject/actor 關係,絕不複製未驗證的宣告。

帳單服務只接受自己的 issuer、簽章金鑰和 audience,並按租戶資源做二次授權。交換判斷可以短暫快取,但撤銷通知和短 TTL 必須滿足目標;所有權杖正文、交換請求和 actor secret 都脫敏。異常回傳統一錯誤,稽核記錄 request ID、subject、actor、audience、scope 和策略版本。」

分步深入設計

第一步:定義權杖與信任邊界

交換服務是授權伺服器或受信任的 STS,不是任意閘道 helper。它配置允許的上游 issuer、JWKS、客戶端認證方式、下游 audience 和策略版本。閘道透過 mTLS、私鑰 JWT 或其他已批准的客戶端認證呼叫端點;不能只靠一個靜態字串表示自己有權代理。

請求最少包含:

text
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=...
subject_token_type=urn:ietf:params:oauth:token-type:access_token
actor_token=...
actor_token_type=urn:ietf:params:oauth:token-type:access_token
audience=https://billing.internal
scope=billing:read

RFC 8693 把請求方視為本次交換的 client;資源伺服器也可以暫時扮演 client,把收到的權杖換成適合後端服務的權杖。這個角色定義不等於授予新權限。

第二步:按來源驗證 subject 與 actor

先按權杖類型選擇驗證器,再檢查簽章演算法白名單、issuer、expnbf、必要的 client、token type 和撤銷狀態。JWKS 依 issuer 快取並按 kid 輪換;未知 key 觸發受控重新整理,不接受演算法降級。

subject 代表被代理的使用者或工作負載。actor 代表實際發起交換的閘道。策略要回答「這個 actor 是否能代表這個 subject 存取這個 audience」,而不是只檢查 actor 是否是某個服務。RFC 8693 的 may_act 可以表達被授權的 actor,交換後權杖可用 act 表達目前 actor;兩者必須來自受信任的簽發方或本地策略。

不要把請求本體的 subtenant_idroles 當作身分事實。交換服務從已驗證權杖和權威租戶目錄重建上下文,並拒絕缺失或衝突的主體資訊。

第三步:計算單調收窄的授權

把授權計算寫成受約束的交集:

text
issued_scope = requested_scope
             ∩ subject_allowed_scope
             ∩ actor_allowed_scope
             ∩ audience_policy_scope
             ∩ tenant_state_scope

請求 scope 為空或超出交集時應拒絕,而不是靜默簽發更寬的預設 scope。audience 必須來自服務註冊表,不能讓閘道任意傳入 URL;每個 audience 綁定允許的 claim、scope、TTL 和資源類型。

租戶狀態、使用者停用、帳單帳戶歸屬和高風險操作可以要求線上策略檢查。權杖裡的 tenant_id 只作為已驗證上下文的一部分,下游仍需把它與資源所有權比較。若一次呼叫要存取多個服務,優先分別交換多個短期權杖,避免一個全能受眾。

第四步:簽發短期、可驗證的下游權杖

新 access token 至少含 isssubaudexpiat、scope、租戶識別、client_id、策略版本和 actor 關係。下游權杖的 exp 應短於上游剩餘壽命和業務風險窗口;不要把 refresh token 當作普通交換結果。

帳單服務固定配置 issuer、audience、JWKS 和允許演算法,並在每次請求執行資源級授權。只驗簽而不驗 aud 會讓一個服務接受發給另一個服務的權杖,正是橫向濫用的入口。必要時可使用 sender-constrained token,讓複製 bearer 值不足以呼叫下游。

第五步:控制快取、撤銷和金鑰輪換

可以快取 issuer 元資料、JWKS 和短期策略結果,但快取鍵要包含 issuer、subject、actor、audience、scope、租戶和策略版本。不能把一個使用者的允許結果重用給另一租戶。權限撤銷先更新權威目錄並發布失效事件;交換端點在五秒內停止簽發,新權杖 TTL 設為最多兩分鐘,帳單服務按風險選擇線上 introspection 或短 TTL 驗證。

快取失效訊息遺失、節點重啟和複製延遲必須演練。JWKS 輪換要允許舊 key 的有界重疊,權杖 kid 可追蹤;撤銷舊簽發金鑰會立即影響所有權杖,不能拿它代替主體級撤銷計畫。

第六步:錯誤、重放與可用性

對未知 issuer、過期、錯誤 audience、不可代理、scope 不足和策略不可用回傳統一的 invalid_grantinvalid_target 類錯誤,不洩露主體是否存在。內部用 request ID 和安全原因碼稽核。

交換請求包含敏感 bearer 值,不應進入 URL、普通日誌、追蹤屬性或錯誤回報。若上游權杖本身可重放,竊取閘道通訊的攻擊者可重複交換;用 TLS、sender constraint、短 TTL、速率限制和一次性風險訊號降低窗口。交換端點的策略儲存不可用時,高風險寫入操作失敗關閉;低風險讀取若允許降級,也要明確最大舊資料時限。

第七步:稽核主體鏈和跨租戶保護

每次簽發記錄 request_id、subject、actor、client、tenant、audience、requested/issued scope、token ID、策略版本、issuer key ID 和決策結果,不記錄權杖正文。下游日誌引用 token ID 與 request ID,把閘道呼叫和使用者操作連成一條證據鏈。

資源服務不應只相信閘道傳來的 X-Tenant-ID。它應從簽名權杖讀取租戶上下文,再檢查資源所有權、帳單帳戶狀態和操作審批。對跨租戶測試、混淆代理、scope 提升、錯誤 audience 和舊 token 重放建立自動化對抗用例。

第八步:用階段性灰度驗證安全不變量

先為一個測試租戶註冊固定 audience 和 scope,驗證舊權杖不能存取帳單服務。再灰度新 issuer/key,觀測交換拒絕率、策略延遲、token TTL、audience 錯誤、快取命中和撤銷傳播時間。逐步擴大到生產租戶,並保留明確的回滾開關:停止簽發新 token,恢復舊的受控客戶端配置,而不是關閉下游所有授權檢查。

高品質示範回答

「我把交換服務當作受信任的 STS。閘道以認證的 client 身分提交使用者 subject_token、自身 actor_token、固定帳單 audience 和最小 scope。服務驗證每個 issuer、簽章、時間、類型、客戶端和可代理策略,再把請求 scope 與 subject、actor、audience、租戶狀態策略求交集;任何擴大權限或任意 audience 的請求都拒絕。

簽發的下游權杖只面向帳單服務,壽命不超過上游剩餘壽命,帶有 subject、actor、tenant、scope、策略版本和 token ID。帳單服務固定信任自己的 issuer、JWKS 和 audience,並執行資源級租戶授權。權杖和交換請求不進日誌,稽核只保留主體鏈與決策元資料。

撤銷先改權威目錄並廣播失效,五秒內停止新交換;下游權杖最多兩分鐘,按風險選擇 introspection 或短 TTL。快取鍵包含完整主體與策略維度,JWKS 輪換允許有界重疊。這樣 token exchange 是權限收窄和責任傳遞機制,不是把使用者身分複製給所有服務。」

常見錯誤

  • 把上游 bearer token 原樣轉發。 下游 audience 不匹配,洩露後橫向影響擴大;應為每個資源換取窄權杖。
  • 只驗證簽章,不驗證 issuer、audience 和 scope。 簽章正確的權杖仍可能發給別的服務。
  • 把 subject 和 actor 合併。 下游無法區分使用者和代理服務,稽核與撤銷都失真。
  • 信任請求本體的 tenant_id 攻擊者可把呼叫改成另一租戶;從已驗證身分和資源歸屬重建。
  • 把 actor 能呼叫交換端點當成可代理任何使用者。 代理關係必須有明確策略和來源。
  • 預設簽發 refresh token。 閘道只需短期 access token,長期憑證會擴大洩露窗口。
  • 快取沒有完整維度或 TTL 過長。 可能跨租戶重用授權或延遲撤銷;限定鍵、版本和傳播目標。
  • 權杖和交換請求寫入日誌。 bearer 值可被重放;只記錄 token ID、公開元資料和原因碼。
  • 一個權杖覆蓋所有下游。 audience 與 scope 過寬;分別交換短期權杖。
  • 只測成功路徑。 過期、錯誤 audience、撤銷、JWKS 輪換、節點重啟和策略故障才暴露真實邊界。

追問與參考答案

may_actact 分別解決什麼問題?

may_act 表達 subject token 允許哪個 actor 代理;act 表達新權杖目前由誰代表誰行動。兩者都不能取代簽章、issuer、audience 和本地授權檢查,且宣告來源必須可信。

為什麼不直接把使用者權杖轉發給帳單服務?

上游權杖通常面向閘道,scope 可能覆蓋多個資源。轉發會擴大受眾、暴露更多身分和權限,並讓下游承擔不必要的信任。交換能簽發只面向帳單、短 TTL、可稽核的權杖。

下游服務要不要即時 introspection?

取決於撤銷目標、流量和可用性。高風險寫入操作可線上 introspection;普通讀取可使用短 TTL JWT。必須記錄最壞延遲,不能同時宣稱離線高快取和即時撤銷。

如果策略服務暫時不可用怎麼辦?

交換端點對高風險操作失敗關閉,避免在未知狀態下簽發。低風險讀取若有明確舊策略快取,可在最長時限內降級,並把降級計入監控和稽核。預設放行會把可用性故障變成越權。

多租戶服務如何防止快取串租戶?

快取鍵至少包含 issuer、subject、actor、tenant、audience、scope 和策略版本;資源服務還要做資源所有權檢查。僅按使用者 ID 或 audience 快取會把一個租戶的決策洩露給另一個租戶。

上游權杖已撤銷但下游權杖仍未過期,怎麼辦?

主體撤銷事件應使交換立即停止,並按風險讓下游 introspection 或消費失效事件。若下游只能離線驗簽,就把 TTL 限制在可接受的最大窗口;無法撤回已提交的操作,需稽核該時間段。

如何做安全回滾?

停止新 exchange、撤回有問題的 audience 或策略版本,保留舊簽發 key 的有界驗證能力,並恢復已驗證的客戶端配置。不要刪除稽核記錄或關閉下游 audience 檢查來「快速恢復」。

公開來源

同類題目