題目與適用場景
一個多租戶 SaaS 的 API 閘道代表登入使用者呼叫帳單服務。身分平台簽發的上游存取權杖面向閘道,帳單服務只接受自己的受眾。閘道需要在使用者請求內取得更窄的下游權杖,同時保留「誰代表誰行動」的可稽核關係。
請設計權杖交換端點、驗證規則、宣告映射、快取、錯誤處理、撤銷與監控。假設閘道是受控伺服器端,瀏覽器不直接保存下游權杖;帳單服務必須拒絕面向其他服務的權杖。
五條不變量:
- 交換服務只接受已驗證且允許被交換的 subject token。
- 新權杖的
audience、scope、租戶與資源範圍不得比上游授權更寬。 - 權杖中的 subject 與 actor 同時可追溯,不能把服務身分偽裝成人。
- 下游服務只信任自己的 issuer、受眾與簽章金鑰。
- 撤銷、過期、金鑰輪換與稽核邊界都有可量測的時限。
面試官在考察什麼
候選人應先區分 token exchange、token forwarding、impersonation 與 delegation。RFC 8693 定義 HTTP/JSON 的 Security Token Service;請求可以帶 subjecttoken,也可以帶 actortoken,回應是普通 OAuth token endpoint 回應的擴充。它不會自動賦予呼叫方更大權限。
第二個訊號是受眾約束。閘道不能把面向自己的 bearer token 原樣轉發給帳單、搜尋與匯出服務。每個下游權杖都要寫入單一 audience,並只授予本次呼叫所需 scope。
第三個訊號是代理關係。只有 subject token 表示的主體允許該 actor 代理時才能交換;mayact、act 等宣告要有明確的信任來源。把客戶端提交的任意 sub 或 tenantid 複製進新權杖屬於越權。
最後要討論失敗模式:交換端點快取了舊授權判斷、下游只驗簽不驗 audience、權杖被記錄到日誌、refresh token 被錯誤交給閘道,以及上游撤銷在下游 TTL 內仍然有效。
回答前要釐清的問題
- 誰是 subject,誰是 actor? 本題中 subject 是登入使用者,actor 是 API 閘道;服務帳號代替使用者時是否允許要寫入策略。
- 交換後的權杖給誰使用? 帳單服務是唯一 audience,還是允許多個資源?多資源會擴大洩露爆炸半徑。
- scope 如何縮小? 上游有
billing:read billing:write,本次請求只需要哪一個? - 租戶邊界在哪裡驗證? 身分宣告、工作階段資料庫和資源資料庫誰是權威?不能信任請求本體租戶欄位。
- 撤銷目標是什麼? 例如上游撤銷後五秒內不再簽發新權杖,現有下游權杖最多還有兩分鐘壽命。
- 閘道是否需要 refresh token? 代理呼叫通常只需要短期 access token;把長期 refresh token 下發給閘道需另行評估。
30 秒回答框架
「我會讓閘道呼叫標準 token endpoint,以 subjecttoken 表示使用者、以 actortoken 表示閘道,並請求固定的帳單 audience 與最小 scope。交換服務先驗證 issuer、簽章、exp、nbf、客戶端認證、subject 的可代理策略與租戶狀態,再透過策略表把上游 scope 映射為允許的下游 scope。新權杖只含帳單 audience、短過期時間、租戶和 subject/actor 關係,絕不複製未驗證的宣告。
帳單服務只接受自己的 issuer、簽章金鑰和 audience,並按租戶資源做二次授權。交換判斷可以短暫快取,但撤銷通知和短 TTL 必須滿足目標;所有權杖正文、交換請求和 actor secret 都脫敏。異常回傳統一錯誤,稽核記錄 request ID、subject、actor、audience、scope 和策略版本。」
分步深入設計
第一步:定義權杖與信任邊界
交換服務是授權伺服器或受信任的 STS,不是任意閘道 helper。它配置允許的上游 issuer、JWKS、客戶端認證方式、下游 audience 和策略版本。閘道透過 mTLS、私鑰 JWT 或其他已批准的客戶端認證呼叫端點;不能只靠一個靜態字串表示自己有權代理。
請求最少包含:
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:readRFC 8693 把請求方視為本次交換的 client;資源伺服器也可以暫時扮演 client,把收到的權杖換成適合後端服務的權杖。這個角色定義不等於授予新權限。
第二步:按來源驗證 subject 與 actor
先按權杖類型選擇驗證器,再檢查簽章演算法白名單、issuer、exp、nbf、必要的 client、token type 和撤銷狀態。JWKS 依 issuer 快取並按 kid 輪換;未知 key 觸發受控重新整理,不接受演算法降級。
subject 代表被代理的使用者或工作負載。actor 代表實際發起交換的閘道。策略要回答「這個 actor 是否能代表這個 subject 存取這個 audience」,而不是只檢查 actor 是否是某個服務。RFC 8693 的 may_act 可以表達被授權的 actor,交換後權杖可用 act 表達目前 actor;兩者必須來自受信任的簽發方或本地策略。
不要把請求本體的 sub、tenant_id、roles 當作身分事實。交換服務從已驗證權杖和權威租戶目錄重建上下文,並拒絕缺失或衝突的主體資訊。
第三步:計算單調收窄的授權
把授權計算寫成受約束的交集:
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 至少含 iss、sub、aud、exp、iat、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 不足和策略不可用回傳統一的 invalidgrant 或 invalidtarget 類錯誤,不洩露主體是否存在。內部用 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 身分提交使用者 subjecttoken、自身 actortoken、固定帳單 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_act 和 act 分別解決什麼問題?
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 檢查來「快速恢復」。