ADR-021: Consent-based impersonation 與 break-glass 分流
ADR 編號: 021 狀態: 已接受 (Accepted) 決策日期: 2026-07-21 決策者: Development Team 取代: 無(延伸 ADR-013 的目標控制面) 被取代: 無 修訂: 2026-08-14——緊急能力改為獨立 break-glass 端點;普通端點不得繞過 consent,並補 refresh-time consent revalidation(見文末修訂紀錄)
摘要
普通模擬端點採同意路或結構路:目標經 email 簽章連結登入後批准,或目標未命中受保護角色/帳號規則且通過 authority 子集反提權政策。緊急越權能力改走獨立 break-glass 端點,要求 impersonation:break-glass、近期 MFA step-up、理由、案件編號與短效 Session;不得在普通端點自動繞過 consent。Consent 與 Session family 綁定,撤銷/到期後不得 refresh。
背景 (Context)
需求(2026-07-21 討論定案)
- 超級使用者角色需要直接模擬任何人的權限。
- 某些角色不能被直接模擬——即使操作者具備
impersonation:enter。 - Consent-based impersonation 適用任何人:任何目標(含受保護角色)經目標本人同意即可被模擬;同意經 email 連結取得,且因客服信箱聯絡是非同步的,連結流程需容忍天級的延遲。
既有機制與缺口
impersonation:enter 閘門管「誰可以模擬」;ImpersonationPolicy SPI+反提權預設(見 impersonation feature)管「可以模擬誰」的結構安全。缺的是:越權能力層(需求 1)、目標族群保護(需求 2——保護理由是隱私/法遵,與權限大小正交)、同意工作流(需求 3)。
先前討論已否決 per-role authority(<role>:impersonation)形狀:與 authority 靜態宣告模型相衝(CUSTOM 角色動態增長)、多角色目標語意懸空、預設姿態兩難。
與 ADR-013 兩概念的關係
| 概念 | 發起者 | 語意 |
|---|---|---|
| Impersonation | 管理者 | 我變成你(無需你知情) |
| Delegation | 被代理者 | 我授權你代理我 |
| Consent-based impersonation(本 ADR) | 管理者發起、目標批准 | 我請求變成你、你點頭後才行 |
本質是「請求驅動的授與、消費在 impersonation 的政策閘門」。不重用 delegation 實作——delegation 的 acting 維度(grantors)語意不同;consent 只是 enter() 的放行條件,模擬語意不變(成為目標本人)。
決策 (Decision)
D-A:普通路與緊急路分流(2026-08-14 修訂)
硬排除(不可協商):服務帳號、自己 ──────────────→ 拒絕
(tenant 模式)跨租戶且無 cross-tenant ─────────→ 拒絕
↓
① 同意路:存在有效 consent(GRANTED、使用窗內)─→ 放行(受保護角色亦可)
② 結構路:目標無受保護角色/帳號 且 目標 authorities ⊆ actor authorities ─→ 放行
↓
其餘 → 拒絕(403)
獨立 POST /impersonation/break-glass:
break-glass authority + 近期 MFA + 理由 + 案件編號 ─→ 短效 non-remembered family
普通路絕不因持有 break-glass authority 而繞過 consent。candidates() 同樣要求
impersonation:enter 並套用普通路政策。可被 break-glass 模擬的受保護 stable subject 不出現在普通候選名單。
D-B:詞彙與受保護角色宣告
| authority | 語意 | 參考連結 |
|---|---|---|
impersonation:enter | 基礎閘門(既有,不動) | 管理角色 |
impersonation:break-glass | 僅供獨立緊急端點越過普通政策 | 僅緊急操作角色;搭配 MFA step-up |
impersonation:cross-tenant | 租戶邊界(既有,正交不動) | — |
受保護角色與帳號集合=設定宣告:app.impersonation.protected-roles 與
app.impersonation.protected-subjects。角色可經有效 consent 放行;stable subject 只能走 break-glass。
D-C:同意記錄權威、簽章連結只是運輸+登入捷徑
兩個效期分離,且同意授權以 consent 記錄為準:
| 效期 | 管什麼 | 預設 | 設定 |
|---|---|---|---|
| 請求效期 | 請求後多久內目標可批准 | 7 天 | app.impersonation.consent.request-ttl |
| 使用窗 | 批准後多久內可模擬 | 48 小時 | app.impersonation.consent.grant-ttl |
email 內的簽章連結走既有 createEventActionLink(REUSABLE、consume 即免密登入目標本人、導向同意頁),效期沿用既有 app.security.signed-link.event-link-ttl-hours(預設 48h)。連結逾期不等於請求失效——目標仍可正常登入、在站內批准(記錄效期 7 天權威);要 email 連結全程有效,調既有設定即可,零新機制。這正是需求 3「非同步、天級延遲」的承接方式。
狀態機:PENDING →(批准)GRANTED →(逾窗)失效/(目標)REVOKED;PENDING →(拒絕)DENIED/(逾期)EXPIRED。
批准後建立 impersonation family 時,Refresh Session 必須保存 consent id、grant 到期時間與 actor 必要 authority。每次 refresh 以該 id 精確重驗 requester/target/status/有效期;目標撤銷 consent 時,同一交易另依 policy reference 主動撤銷全部既有 family。兩條機制互補:主動撤銷縮短 暴露窗,refresh-time revalidation 則是跨節點與失敗重試下的最終安全線。
D-D:四個拍板
| 板 | 決定 | 理由 |
|---|---|---|
| 同意綁定 | 綁發起請求者(requester × target 精確配對) | 最小意外;輪班交接需求出現時再放寬為設定 |
| 使用語意 | 使用窗內多次 | 支援會談常需反覆進出;窗短已限縮暴露面 |
| 同意身分強度 | 需登入(簽章連結 consume 即登入目標本人,批准是 authenticated 呼叫、service 驗證操作者=目標) | 受保護角色值得保護,同意的身分強度不該只是「信箱持有」 |
| 緊急命名 | impersonation:break-glass | 清楚表達需額外控制的緊急程序,不是普通端點的 unrestricted bypass |
D-E:落地為 optional 組合 feature(歷史決策,已由 2026-07-21 與 2026-08-14 修訂)
impersonation-consent(requires auth+impersonation+signed-link+notification;notification 為 beanRequires——經 jar 的 NotificationEvent 發布、無 app 層 import 邊):
- consent entity/repository/service/controller(請求、批准、拒絕、撤回、我的請求/待批清單)
- email 通知:顯式收件人+
deepLink(passwordless 先例);範本走訊息束notification.impersonation-consent-request.email.* ConsentAwareImpersonationPolicybean 實作整個格架——bean 存在即覆蓋反提權預設(SPI 既有機制),impersonationcore slice 一行不改;未裝本 feature 的專案維持現行為impersonation:break-glass由本 feature 的AuthorityContributor宣告;seed 的revokedAuthorities會從預設管理角色移除舊impersonation:unrestricted與先前誤配的impersonation:break-glass- reference seed 不把 break-glass 指派給一般/SUPER_ADMIN 角色;只有企業 IdP 能提供
amr/auth_time的專用 operations 角色才應取得此 authority - 全程 audit:
IMPERSONATION_CONSENT_REQUESTED/GRANTED/DENIED/REVOKED
考量的方案 (Alternatives)
| 方案 | 不採理由 |
|---|---|
per-role authority(<role>:impersonation) | 詞彙隨角色動態增長、多角色語意懸空、預設姿態兩難(先前討論已否決) |
| 重用 delegation 實作同意 | acting 維度語意不同(代理 vs 成為);consent 只是 enter 的放行條件,不該改變模擬語意 |
| 同意免登入(信箱持有即同意) | 受保護角色的同意不該用最弱身分強度;簽章連結本就能免密登入,「需登入」零額外成本 |
| consent 連結自帶長效期(新 purpose+自訂 consume) | 需複製 consume→session 中段或改 seam API;記錄權威+既有 event link 達成同等效果、零新機制 |
| 把格架塞進 impersonation core | 拉入 notification/signed-link 依賴,required 化了本該 optional 的組合;SPI 讓 core 零改動。(本列同日被修訂推翻——依賴前提經 fleet 檢視不成立,見「修訂紀錄」) |
後果 (Consequences)
正面
- 三種真實營運姿態(超級使用者、受保護族群、非同步同意)一次補齊;未裝者行為不變。
- 全部用既有積木;
impersonationcore 與既有下游零受影響。
負面 / 成本
- 同意流的 UI(同意頁、待批清單、發起請求入口)屬前端工作,另走 US 流程。
- ict 的
SignedLinkAppService為 localSeam(已客製)——sync 本 feature 時須確認其 API 含createEventActionLink(見「誠實的成本」family)。
中性
- 受保護角色集合是產品政策(per-project 設定),方法論不給預設名單。
2026-07-21 落地檢查清單(歷史快照)
以下項目記錄當時交付狀態;其中 unrestricted 路徑已由 2026-08-14 修訂取代,不代表目前契約。
-
feature/impersonationconsent/:entity+repository+service(Clock 注入)+controller+ConsentAwareImpersonationPolicy+authority 常數與 SeedProvider+properties -
config/feature-impersonation-consent.yml+spring.config.import登錄(ConfigFragmentsTest 守衛) - 訊息束範本(messages / zh_TW / en)
-
Role.json:SUPER_ADMIN +impersonation:unrestricted - catalog 登錄(requires+beanRequires: notification);I3 PASSED
- 格架 policy 單元測試(4 案)+consent 流程 IT(5 案:受保護角色 direct 403、請求冪等、批准後窗內多次 200、撤回/拒絕後 403、非目標裁決 403、已裁決再批 409、unrestricted 直通)
- app-server 全量 build+完整 I4(ownership 組裝樹)綠;本 ADR 定案(Proposed → Accepted)
- llms.txt
拍板紀錄(2026-07-21)
維護者提出三條需求(超級使用者直接模擬、受保護角色、consent 適用任何人+長效連結)並拍板四個設計題:綁請求者、使用窗內多次、同意需登入、命名 impersonation:unrestricted。實作過程一項工程約束記錄在案:ImpersonationConsentIT 刻意加入共用 test context(執行期切換 properties bean、測後還原)而非 @DynamicPropertySource 另開 context——測試 JVM 的 Ehcache off-heap 預算(MaxDirectMemorySize=2g)已無另開 context 的餘裕,且開發機記憶體有限(見 app-server build.gradle.kts 註解)。
修訂紀錄(2026-07-21):consent 併入 impersonation feature
改判:廢除獨立的 impersonation-consent feature,consent 機制(entity/repository/service/controller、ConsentAwareImpersonationPolicy、impersonation:unrestricted seed、TTL 設定)併入 impersonation feature 單一出貨。impersonation 的 requires 變為 acting+auth+notification+signed-link(notification 為 beanRequires)。
理由(維護者定性 + fleet 檢視):
- Consent 是模擬的正確設計,不是加值選配——無同意的模擬(結構路)保護權限結構、不保護隱私,對資安/法遵而言是權宜路徑。單獨出貨的 base impersonation 形狀,會誘使下游「因資安疑慮不裝、或鎖死到難用」,反而傷害採用。合併使完整三路格架成為唯一出貨形狀:consent 的可用性普遍保證,嚴格程度(受保護角色集合、實務上走哪條路)仍為 per-project 政策。
- D-E 原本的依賴重量論證前提不成立——
notification+signed-link是下游優先採用的 feature,選到 impersonation 時幾乎必已在場;且 requires 強制的是 code slice 在場、非 mail 設定生效,無 mail 環境仍可安裝(consent 路休眠、結構路照常)。 - 遷移窗口最低點——改判當下受 catalog 治理的下游僅 ict-server 一個(且未裝 consent),合併成本為歷史最低。
保持不變:三路格架語意(D-A)、詞彙(D-B)、記錄權威與效期(D-C)、四個拍板(D-D);SPI 仍存在(下游仍可自訂 policy 覆蓋)。
落地:feature/impersonationconsent/ package 併入 feature/impersonation/;config fragment 更名 feature-impersonation.yml;catalog 條目合併;I3/build/I4(ownership)全綠。
連帶修正(首次 fleet sync〔ict〕發現,同日):同意深連結原以 email+attributes Map 簽發,違反 ADR-018 自己的身分模型(呼叫端已握有 Account 卻走 email 弱鍵、多命中時降級不發連結),且 Map.of("consentId") 是死資料——consume→exchange 只傳 access/refresh token,連結 attributes 到不了前端。修正:SignedLinkAppService(參考 seam)補 username 鍵進入點 createEventActionLink(username, target, app);consent 深連結改以 username 簽發、consentId 掛 target query param、同意頁前端由 app.impersonation.consent.app 宣告(多前端專案;缺省=預設前端)。此簽章恰與 ict 既有 localSeam 一致——下游客製先一步走到 ADR-018 方向,核心反向對齊(reflow 語意)。
修訂紀錄(2026-08-14):break-glass 分流與 Session policy 綁定
舊能力路 impersonation:unrestricted 會讓普通端點在缺少額外身分證明、理由與案件脈絡時直接
越過 consent,無法證明是正常客服操作還是緊急事件,故改判為獨立
impersonation:break-glass 端點。Reference adapter 驗證 IdP JWT 的 amr/auth_time;local login
沒有 step-up 證明時 fail closed。Break-glass family 固定 non-remembered、短 TTL 與絕對期限。
同時把 consent 從「只在 enter 檢查」提升為 Session family policy:框架保存 policy binding,
refresh 重驗 actor/subject 狀態、必要 authority、絕對期限及 app hook;consent revoke 依 id 主動
撤銷 family。候選端點也改以 impersonation:enter 正式保護。第一版 reference 實作固定 pure
impersonation,不繼承目標 delegation,並明確禁止 nested impersonation。
Reference break-glass 僅提供 server API,不提供 local-login demo UI/MSW handler:近期 MFA 必須由 企業 IdP 完成,前端也必須先走該 IdP 的 step-up journey,不能以表單或 mock 宣稱完成 MFA。 首次成功進入會把案件登錄為 OPEN;結案端點把案件持久改為 CLOSED、依 case number 撤銷全部 family,refresh 亦逐次重驗案件仍為 OPEN。正式產品應依 ticket system retarget 本地 reference registry。
相關
- ADR-013——模擬/委派的基礎機制;本 ADR 是其目標控制面的延伸
- impersonation feature 的
ImpersonationPolicySPI 與AntiEscalationImpersonationPolicy(結構路預設) - signed-link feature(
createEventActionLink:consume 即登入的事件深連結);notification feature(顯式收件人 email) 19-env-config.md——TTL 與受保護角色的設定治理