CR #212 » CR-011_服務間呼叫缺呼叫者身分識別致授權白名單無法實作.md
CR-011 服務間呼叫欠缺呼叫者身分識別 → SAD-002 §5 授權白名單技術上無法實作
| 項目 | 內容 |
|---|---|
| 文件編號 | CR-011 |
| 版本 | 1.3(§11 gateway/bff 之爭議BEAM PMO 2026-09-21 17:10 核定採 B;轉傳機制已執行並測試通過)⚠️ 1.2 版執行後未回填本欄,本次一併更正 |
| 狀態 | ✅ 已核定:身分識別採 A(每模組一組專屬 M2M client);gateway/bff 之呼叫方式採 B(轉傳使用者權杖)。✅ 階段一、二皆已完成並實測 |
| 提出日期 | 2026-09-21 |
| 提出者 | WorkBuddy AI(開發團隊) |
| 關聯 |
CR-010、SAD-002 §5、SAD-005 §3.2/§3.4(身分轉傳原則)、SCS-022 §7.9/§7.10(白名單)/§7.11(Token Relay)
|
| 嚴重度 | 🟡 中(非可用性缺陷;屬「最小權限」未落實) |
1. 背景
CR-010 修復「服務間呼叫未帶授權標頭」後,服務間呼叫能通過認證了。
但認證(你是誰)與授權(你能做什麼)是兩件事:
目前狀態:持有有效服務權杖者,可呼叫任何服務的任何端點。
SAD-005 §3.2 明訂「授權範圍:每個服務僅能呼叫其被允許之服務端點(見 SAD-002 §5)」,
SAD-002 §5 亦有完整之服務互動矩陣。規格已備,實作卻付之闕如。
本次嘗試依矩陣實作白名單時,發現先決條件不成立——詳 §2。
2. 實測證據(2026-09-21)
2.1 權威來源:SAD-002 §5 互動矩陣(節錄)
S=同步(HTTP)。以 statement 為例:
| 呼叫者 \ 被呼叫 | auth | limit | card | party | product | posting | payment | statement | pricing | … |
|---|---|---|---|---|---|---|---|---|---|---|
| statement | — | — | — | S | S | S | — | — | — | … |
→ statement 僅應能同步呼叫 party/product/posting(+ pricing,見完整表);
呼叫 authorization/limit/card/payment 應被拒。
2.2 實測:被呼叫端拿到的 token 無法識別呼叫者
以 client_credentials 取 token(Keycloak 實測 HTTP 200),解開 claims:
azp = cardcore-services ← ★ 所有服務皆同一值
client_id = cardcore-services
aud = ['cardcore-api', 'account']
scope = profile email
typ = Bearer
sub = e2456954-9315-45c5-835f-fc0e32b43dd2
realm_access = {roles: [default-roles-cardcore, offline_access, uma_authorization]}
resource_access = {account: {roles: [manage-account, manage-account-links, view-profile]}}
全部 claim keys:acr, aud, azp, clientAddress, clientHost, client_id, email_verified, exp, iat, iss, jti, preferred_username, realm_access, resource_access, scope, sub, typ
★ 關鍵:
azp/client_id恆為cardcore-services,
realm_access.roles全為通用角色(無任何服務專屬角色)。
17 個服務取到的 token 內容完全一致(僅jti/exp/sub不同)。
→ 被呼叫端無從判斷「是哪個服務在呼叫我」。
2.3 程式端:完全沒有讀取呼叫者身分
common-spring 之 src/main/java 內,azp/aud 僅出現於出站取 token 之情境
(ClientCredentialsTokenProvider、OutboundTokenAutoConfiguration 之 clientId),
沒有任何入站授權判斷讀取 azp 或 aud。
3. 根因:這是 CR-010 方案 A 的既有代價,不是新缺陷
CR-010 §4 三方案中,BEAM PMO 核定採 A:單一服務帳號(client_credentials),
其優點為「IdP 端已備妥、不新增依賴、集中於 common-spring,17 服務不必重工」。
該優點的反面即是本次問題:既然 17 個服務共用同一組 client,
身分就只能是「某個持有 cardcore-services 憑證的人」,而不是「statement-service」。
換言之:
CR-010解決了「有沒有帶證件」;
本次要解決的是「證件上要不要寫名字」——而現在的證件沒寫名字。
4. 選項與建議
方案 A ⭐ 每服務一組 M2M client(azp 即服務身分)
Keycloak 建立 17 組 confidential client(cc-stmt/cc-posting/…),
各服務以其專屬 client-id/client-secret 取 token → azp 即為服務身分;
被呼叫端依 SAD-002 §5 矩陣(轉置為「我允許誰呼叫我」)比對 azp。
| 優點 | 身分識別乾淨且可稽核;真正落實 SP-02 最小權限;白名單邏輯單純(比對 azp) |
| 代價 |
17 組 client + secret 之生命週期管理(須由 KMS 提供、輪替);Keycloak realm 組態擴增;各服務組態之 client-id 不再相同(模板之 $name 可自動化) |
| 風險 | secret 管理若未制度化,會比單一 client 更難維運 |
方案 B 維持單一 client,改以 audience 區分
每個服務定義專屬 audience(如 cardcore-posting);呼叫端取 token 時指定目標 audience。
| 優點 | client 仍為單一,管理負擔低 |
| 代價 | 呼叫端須為每個目標服務各取一份 token → 現行「單一快取」須改為多鍵快取;且 audience 語意是「這個 token 給誰用」,不能證明呼叫者是誰——只能限縮「用途」,無法限縮「來源」 |
| 風險 | 看似解決、實則只做半套;與 SAD-005 §3.2「每個服務僅能呼叫其被允許之服務端點」之來源限縮語意不符 |
方案 C 暫不實作,維持現狀並列為已知落差
| 優點 | 不動程式、不增維運負擔 |
| 代價 | 與 SAD-005 §3.2 之授權範圍要求明確不符;違反 SP-02 最小權限;客戶資安審查時須誠實揭露 |
建議
建議採 A,理由:
- 唯有 A 能真正表達「來源」限縮,符合
SAD-005 §3.2原意;B 只限縮用途。 - 憑證管理本來就該由 KMS 承擔(本地已有
~/.config/cardcore/*.env外置慣例,見#9),
不是「多幾組就做不了」的問題,而是「早晚要做」的基礎設施。 - 17 組 client 可由產生器/腳本批次建立,不必手工。
若考量本期時程,C 亦可接受,但須於 SCS-022 §10 與 SAD-005 §3.2 明列為已知落差,
不得以「已有服務權杖」宣稱授權範圍已落實。
5. 設計(採 A)
5.1 命名規則
| 項 | 規則 | 例 |
|---|---|---|
clientId |
cc-<模組名> |
cc-statement-service |
secret(本地) |
cc-<schema>-dev-secret;無 schema 者 cc-<模組名>-dev-secret
|
cc-stmt-dev-secret、cc-api-gateway-dev-secret
|
| 環境變數 |
CARDCORE_M2M_CLIENT_SECRET_<後綴>;後綴=領域服務採 schema 大寫(STMT),平台模組採模組名大寫且 -→_(API_GATEWAY) |
CARDCORE_M2M_CLIENT_SECRET_STMT |
⚠️ 後綴規則必須與
gen_service_skeleton.py之m2m_env_suffix完全一致,
兩處皆為同一規則之實作(已於產生器註解標明),改動時須同步。
5.2 為何 secret 須逐模組不同(而非共用同一環境變數名)
本地開發會同時啟動多個服務(如 statement+posting+pricing)。
若共用 CARDCORE_M2M_CLIENT_SECRET 一名,所有服務讀到同一個值 →
azp 全部相同 → 又回到「認不出呼叫者」。
故必須以 $m2m_env_suffix 產生逐模組之變數名。
5.3 階段劃分
| 階段 | 內容 | 狀態 |
|---|---|---|
| 一、身分識別 | 19 組專屬 client + 組態使 azp =服務身分 |
✅ 完成並實測(§9) |
| 二、白名單機制 | 入站依 SAD-002 §5 矩陣比對 azp,不符回 403;白名單未設定時啟動失敗(fail-closed) |
⬜ 未施作 |
6. 影響範圍
| 產出 | 變更 |
|---|---|
tools/gen_keycloak_m2m_clients.py(新增) |
批次建立/檢查 19 組專屬 client;--check/--write/--print-env/--sync-realm-json。零相依(stdlib only),比照既有 gen_*.py 之「只建立不存在者」慣例 |
Keycloak realm cardcore
|
新增 19 組 confidential client(17 領域 + api-gateway/bff-web),各含 audience-cardcore-api mapper |
ops/local/config/keycloak-realm.json |
同步寫入 19 組(環境重建之可重現性——否則重建後 client 全數消失) |
tools/gen_service_skeleton.py |
①新增 m2m_env_suffix 佔位符;②T_APP_LOCAL_YML 與 T_APP_LOCAL_YML_CORE 兩模板之 client-id 改 cc-$name、client-secret 改 CARDCORE_M2M_CLIENT_SECRET_$m2m_env_suffix
|
19 份 application-local.yml
|
逐模組改為專屬 client(產生器只寫不存在者 → 模板+既有檔兩邊都動) |
~/.config/cardcore/m2m.env |
改為 19 組變數(repo 外、權限 600);舊共用變數保留但註明停用 |
| 程式 |
未改(OutboundTokenAutoConfiguration/ClientCredentialsTokenProvider 原樣可吃新組態) |
7. 風險揭露
| # | 風險 | 說明 |
|---|---|---|
| 1 | 白名單機制尚未施作 | 本次僅完成「認得出呼叫者」;「只放行該放的」仍待階段二。現況仍是「持有有效權杖可呼叫任何端點」——此為自 CR-010 起即存在之狀態,非本次引入
|
| 2 | 19 組 secret 明文見於 ops/local/config/keycloak-realm.json
|
本地 keycloak 匯入用,與既有 cardcore-services 同性質(既有狀況,非本次引入);真實環境一律由 KMS 提供,不得進版控 |
| 3 | 憑證管理負擔上升 | 由 1 組增為 19 組;輪替須制度化(本地已有 ~/.config/cardcore/*.env 外置慣例,見 #9) |
| 4 | 舊共用 client cardcore-services 仍存在
|
未刪除(避免破壞既有);任何持有其 secret 者仍可取到「無身分」之 token。階段二上線前應評估是否停用 |
| 5 | 尚未於 SIT/UAT/PROD 驗證 | 僅本機實測 |
8. 核定結果
✅ BEAM PMO 2026-09-21 裁定:採 A(每服務一組 M2M client)。
9. 執行結果(階段一,2026-09-21 實測)
9.1 IdP
gen_keycloak_m2m_clients.py --check → 缺漏 17;--write → 建立 17;
再補 api-gateway/bff-web → 建立 2(共 19);--sync-realm-json → realm json 內 cc-* 19 組。
9.2 ★ 核心實測:各服務以專屬 client 取 token,azp 即服務身分
| client | azp |
|---|---|
cc-statement-service |
cc-statement-service ✅ |
cc-posting-service |
cc-posting-service ✅ |
cc-pricing-service |
cc-pricing-service ✅ |
(對照)舊共用 cardcore-services
|
cardcore-services(無身分) |
→ 三者互不相同,方案 A 之先決條件成立;aud 維持 ['cardcore-api','account']。
9.3 組態與啟動實測
- 19 份
application-local.yml全數改為cc-<name>+ 逐模組 secret 變數 - 憑證:19 組寫入
~/.config/cardcore/m2m.env(權限 600,載入測試變數數=19) -
啟動實測(
statement-service,CARDCORE_SECURITY_ENABLED=true):-
服務間呼叫憑證已啟用:client_credentials(client=**cc-statement-service**)✅ -
認證機制已啟用:JWT(OAuth2 Resource Server);公開路徑 4 個✅ -
Started StatementServiceApplication in 18.229 seconds、8092 監聽 ✅(實測後已停止)
-
-
gen_service_skeleton.py --check:25 模組、缺漏 0
9.4 階段二:白名單機制(2026-09-21 實測)
單一真實來源:SAD-002 §5 互動矩陣 → tools/gen_service_acl.py --write
→ backend/platform/common-spring/src/main/resources/cardcore-service-acl.yml。
本檔不手改——改動請修 SAD-002 §5 後重跑產生器,避免出現兩份真相。
| 產出 | 說明 |
|---|---|
tools/gen_service_acl.py(新增) |
解析矩陣 markdown 表格 → 轉置為「被呼叫端 → 允許之 azp」;只取同步(S),事件(A)不管制 |
cardcore-service-acl.yml |
19 條目(17 領域 + api-gateway/bff-web) |
ServiceAcl.java |
載入/解析/allowedCallersOf/isServiceAccount/isAllowed。查無自身條目即拋錯(fail-closed,非靜默空集合);解析失敗一律拋錯 |
ServiceCallerAclManager.java |
AuthorizationManager<RequestAuthorizationContext>;分流「服務帳號 → 查 ACL」與「使用者 token → 放行」 |
SecurityAutoConfiguration |
.access(new ServiceCallerAclManager(...)) 取代 .authenticated()
|
ServiceAclTest(10 例)+ ServiceAclFilterTest(4 例) |
後者為過濾器層級——若忘了掛上 HttpSecurity,前者仍會全綠(假綠),故必須走完整 filter chain 斷言 |
⚠️ 產生器修正(矩陣本身之不一致):互動矩陣表頭用縮寫 auth、呼叫者列卻用全名 authorization
→ 產生器原先略過該列,使 card/limit/product 各少一個允許者。
修法:SHORT_TO_NAME 同時收錄兩者。(矩陣不一致本身應回頭修 SAD-002 §5,此處僅讓產生器容錯。)
★ 實測(真實 Keycloak token 打真實 posting-service,8087,
GET /api/v1/accounts/{id}/balances)
| # | 情境 | 結果 |
|---|---|---|
| ① | 無憑證 |
401 CC-PLT-4010
|
| ② |
cc-statement-service(SAD-002 §5 為 S) |
404 CC-POST-4040 —— ACL 通過(查無該分戶,屬業務錯誤) |
| ③ |
cc-clearing-service(矩陣非 S) |
403 CC-PLT-4030 ✅ |
| ④ | cc-bff-web |
403 CC-PLT-4030 ⚠️ —— 見 §11 |
- 服務端日誌:
⛔ 服務間呼叫遭拒:本服務=posting-service 呼叫者=cc-clearing-service —— 未列於 SAD-002 §5 互動矩陣之允許清單,
其 traceId 與 HTTP 回應之traceId一致 - 啟動日誌:
認證機制已啟用…;服務間呼叫 ACL 已載入(19 個條目,本服務允許 **4** 個呼叫者)
(posting-service之 4 個=dispute/payment/rewards/statement,與矩陣相符)
★ 判定力驗證(負樣本,證明測試不是「寫了就綠」)
| 竄改 | 轉紅案例 |
|---|---|
| 決策一律放行 | 5 例:ServiceAclTest ⑧⑨、ServiceAclFilterTest ②④、SecurityFailClosedTest
|
| 決策一律拒絕 | 3 例:ServiceAclTest ⑨、ServiceAclFilterTest ①③ |
→ 兩個方向皆精準反應,其餘案例不受影響。
⚠️ 本次實作抓到的自身缺陷(誠實揭露)
初版以 auth.isAuthenticated() 判斷,但 AnonymousAuthenticationToken.isAuthenticated() 為 true
(Spring Security 刻意如此設計)→ 無憑證請求落入「非服務帳號一律放行」分支而被放行,實測回 200。
此缺陷單元測試沒抓到——負樣本用了 TestingAuthenticationToken(false),非真實型別;
是 ServiceAclFilterTest ④(走完整 filter chain)抓到的。
修法:①顯式排除 AnonymousAuthenticationToken;②單元測試負樣本改用真實型別。
→ 教訓:負樣本必須覆蓋所有判斷點,且必須用真實型別。
9.5 測試數
common-spring:71 → 85 例(+14),0 失敗、0 略過。
11. ✅ api-gateway/bff-web 之呼叫方式——BEAM PMO 2026-09-21 17:10 核定採 B(轉傳使用者權杖)
實測:以 cc-bff-web 之 token 呼叫 posting-service → 403 CC-PLT-4030。
原因:SAD-002 §5 互動矩陣僅涵蓋 17 個領域服務,不含 gateway/bff。
產生器對二者補 [] 的結果是——它們不允許任何服務帳號呼叫,同時沒有任何服務允許它們呼叫。
(此與 CR-010 §9.7 之根因同源:SCS-022 §3 原記載「並無 gateway/bff 模組」,致二者被規格排除。)
現況:兩模組皆為骨架(api-gateway 無路由設定、bff-web 無 Feign client),
故目前無立即故障;但一旦 bff 以 M2M 呼叫領域服務,即全數 403。
選項
| 做法 | 後果 | |
|---|---|---|
| A | 於 SAD-002 §5 新增 api-gateway/bff-web 兩列,允許其呼叫領域服務 |
二者取得「可呼叫所有服務」之超級身分;且服務端失去使用者身分 |
| B | 明訂 gateway/bff 轉傳使用者 token,不使用 M2M 呼叫領域服務 | 現行 ACL 無須修改;使用者 token(azp 非 cc-)本就不受 ACL 管制,交角色授權 |
我的看法:傾向 B
- gateway/BFF 是「使用者的代理人」——改用 M2M 會使服務端失去使用者身分,
使用者層級授權與「誰做的」稽核皆無從實作(銀行核心之硬需求)。 - 現行 ACL 設計已預留此路徑:使用者 token 一律放行、交由角色授權,正是預期 gateway/bff 轉傳。
- 採 A 等於給 gateway/bff 超級權限,違反最小權限原則。
⚠️ 若採 B,CR-010 §9.5 為 gateway/bff 補上的出站 M2M 組態即不應被使用——
須於 SAD-005 明訂「轉傳原則」,避免日後誤用。
影響範圍(2026-09-21 15:50 複核——三件事皆為實測,非推測)
| # | 查證項 | 結果 |
|---|---|---|
| 1 | gateway/bff 是否真的只是骨架 | ✅ 屬實:兩模組 src/main/java 各僅 1 個 Application 類別,加 application.yml/application-local.yml 與 1 個 LayeringTest——無路由設定、無 Feign client、無任何出站呼叫
|
| 2 | WebUI 目前是否經過 gateway/bff | ❌ 沒有——frontend/vite.config.ts 之 proxy 直連各領域服務(8081/8082/8083/8084/8087/8088/8089/8091/8092),且註解明載「正式環境應統一由 API Gateway(8100)出口」 |
| 3 | 使用者 token 是否受影響 | ❌ 不受影響——azp=cardcore-web(非 cc-),ServiceCallerAclManager 判定「非服務帳號」→ 放行(SCS-022 §7.10 規則 3) |
結論:目前影響為 0——沒人呼叫 gateway/bff,WebUI 也不經過它們。
但這是已預定的地雷:vite.config.ts 之設計註解已明訂正式環境要走 Gateway 8100,
且 CR-010 §9.5 已為二者補上出站 M2M 組態——一旦有人照該組態實作 bff 呼叫,即全數 403。
附帶實測(2026-09-21 15:50):本 ACL 之嚴格程度
以真 Keycloak token 打 installment-service 8089:
呼叫者 azp
|
結果 |
|---|---|
cc-statement-service |
403 |
cc-bff-web |
403 |
cc-api-gateway |
403 |
⚠️ 三者皆 403 並非缺陷——SAD-002 §5 矩陣之 installment 欄(被呼叫端)全列皆 —,
即無任何服務以同步方式呼叫分期服務,故其允許清單本來就是 [](產生器轉置結果正確)。
★ 但這揭示一項性質:此白名單極為嚴格——任何「矩陣未畫到」的同步呼叫都會被擋。
日後若出現新的同步呼叫需求(例如帳單需同步讀取分期期數),必須回頭改 SAD-002 §5 再重跑產生器,
不得直接改 cardcore-service-acl.yml(該檔由產生器產出,禁止手改)。
✅ 執行結果(2026-09-21 18:00,採 B 後之落地)
| # | 項目 | 內容 |
|---|---|---|
| 1 | 設計層 |
SAD-005 → 1.3:新增 §3.4「身分轉傳原則(Token Relay)」;並更正 §3.2 之過時記載(「端點白名單 ⚪ 未實作」→ ✅ 已實作) |
| 2 | 規格層 |
SCS-022 → 新增 §7.11「使用者權杖轉傳(Token Relay)」(模式組態、行為規格、fail-closed、與 ACL 之關係、已知限制) |
| 3 | 程式 | 新增 OutboundMode(CLIENT_CREDENTIALS/TOKEN_RELAY)、RelayingOutboundTokenProvider;SecurityProperties.Outbound 增 mode;OutboundTokenAutoConfiguration 依模式分流 |
| 4 | 組態 |
api-gateway/bff-web 之 application-local.yml 改為 mode: token-relay,並移除 client-id/client-secret;產生器 T_APP_LOCAL_YML_CORE 模板同步(--check 25 模組、缺漏 0) |
| 5 | 測試 | 新增 RelayingOutboundTokenProviderTest 5 例、OutboundTokenGuardTest 增 1 例(★「入站啟用 + 無憑證 + token-relay」啟動成功);common-spring 85 → 91 例,0 失敗 0 略過
|
★ 關鍵決策:以「模式切換」而非「刪除出站組態」——OutboundTokenAutoConfiguration 之 fail-closed
為「入站啟用 + 出站憑證未設定 → 啟動期失敗」。若直接刪除 gateway/bff 之出站組態,
二者於 enabled=true 時會起不來。故新增 TOKEN_RELAY 模式,
讓「不需要 M2M 憑證」成為可被表達的合法狀態,而非「設定缺漏」。
★ 實測抓到之陷阱(本次):JwtAuthenticationToken 單引數建構式(JwtAuthenticationToken(Jwt))
不會呼叫 setAuthenticated(true) → 其 isAuthenticated() 為 false;
僅雙引數(帶 authorities)版本才會(反查 Spring Security 7.1.1 位元碼確認)。
生產路徑(JwtAuthenticationProvider)用雙引數版本,故行為正確;
但測試若誤用單引數建構式,會得出「JWT 身分未認證」之錯誤結論。已立為測試案例 ⑤。
⚠️ 未驗證:gateway/bff 仍為骨架(無路由、無 Feign client),轉傳尚無端到端實測
(無呼叫端可測)。本節屬機制先行,待二者實際接線時須補端到端驗證。
12. 修訂記錄
| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0 | 2026-09-21 | 初版。嘗試依 SAD-002 §5 實作服務對服務端點白名單時,實測發現先決條件不成立:所有服務共用單一 M2M client,azp 恆為 cardcore-services 且無服務專屬角色 → 被呼叫端無法識別呼叫者。指出此為 CR-010 採方案 A 之既有代價,非新缺陷。提列三方案並建議採 A |
| 1.1 | 2026-09-21 |
BEAM PMO 核定採 A。執行**階段一(身分識別)**並實測通過:①新增 tools/gen_keycloak_m2m_clients.py(--check/--write/--print-env/--sync-realm-json,零相依);②建立 19 組專屬 client(17 領域 + gateway/bff)並同步回 keycloak-realm.json(環境重建之可重現性);③產生器新增 m2m_env_suffix 佔位符,兩個模板與 19 份既有 yml 全改為 cc-$name + 逐模組 secret;④憑證 19 組置於 ~/.config/cardcore/m2m.env(600)。★ 核心實測:各服務以專屬 client 取 token → azp 分別為 cc-statement-service/cc-posting-service/cc-pricing-service(互不相同);啟動實測見「client=cc-statement-service」。⚠️ 階段二(白名單機制:比對 azp、不符回 403、未設定即啟動失敗)尚未施作——現況仍為「持有有效權杖可呼叫任何端點」,此狀態自 CR-010 起即存在、非本次引入 |
| 1.2 | 2026-09-21 | 執行階段二(白名單機制)並實測通過:①新增 tools/gen_service_acl.py,以 SAD-002 §5 為單一真實來源轉置產生 cardcore-service-acl.yml(19 條目,不手改);②新增 ServiceAcl/ServiceCallerAclManager(AuthorizationManager),掛載於 SecurityAutoConfiguration 之 .access(...);③新增 ServiceAclTest(10 例)與 ServiceAclFilterTest(4 例,過濾器層級)。★ 真機實測(真 Keycloak token → 真 posting-service):無憑證 401/cc-statement-service(矩陣 S)通過/cc-clearing-service(非 S)403 CC-PLT-4030,服務端拒絕日誌之 traceId 與回應一致。★ 負樣本驗證:決策改一律放行 → 5 例轉紅、一律拒絕 → 3 例轉紅,判定力確認。⚠️ 本次實作抓到自身缺陷:初版未排除 AnonymousAuthenticationToken(其 isAuthenticated() 為 true)→ 無憑證請求被放行(實測回 200);單元測試負樣本用錯型別故未抓到,由 ServiceAclFilterTest ④ 抓出,已修並改用真實型別。⚠️ 新發現待裁定(§11):cc-bff-web 呼叫領域服務亦為 403——SAD-002 §5 未涵蓋 gateway/bff;兩模組現為骨架故無立即故障,但傾向採「轉傳使用者 token」而非放行 M2M。common-spring 測試 71 → 85 例全綠 |
| 1.3 | 2026-09-21 |
BEAM PMO 2026-09-21 17:10 核定採 B(轉傳使用者權杖),本版執行並測試通過:①設計層 SAD-005 → 1.3(新增 §3.4 身分轉傳原則;更正 §3.2「白名單未實作」之過時記載);②規格層 SCS-022 新增 §7.11(Token Relay);③程式 新增 OutboundMode/RelayingOutboundTokenProvider,OutboundTokenAutoConfiguration 依模式分流;④組態 gateway/bff 改 mode: token-relay 並移除 client 憑證(產生器模板同步);⑤測試 common-spring 85 → 91 0 失敗。★ 何以是模式切換而非刪除組態:fail-closed 會使刪除後於 enabled=true 啟動失敗。★ 實測陷阱:JwtAuthenticationToken 單引數建構式之 isAuthenticated() 為 false。⚠️ 另:1.2 版執行後未回填檔頭版本欄(仍記 1.1),本次一併更正 |