外部身分連結
概述
external-identity Feature 把外部安全域的不可變身分(例如 OIDC 的 issuer + subject)連到
應用自己的 Account。它只負責「誰對應到哪個 Account」,不擁有 OIDC redirect、token
exchange 或 session 簽發;這些協定流程由 oidc-login 等上層 Feature 組裝。
快速開始
保留 feature-external-identity.yml 的 production-safe 預設後啟動應用;新資料庫會依現有
spring.jpa.hibernate.ddl-auto 建立 schema。先以管理者 bearer token 連結 Account:
curl -X POST \
http://localhost:8080/app-server/api/v1/accounts/{accountId}/external-identities \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "oidc",
"provider": "https://login.example.com",
"subject": "00u123456789"
}'
之後 oidc-login 以同一組 exact issuer + subject 即可解析到該 Account。第一次登入自動
mapping 預設關閉,不需 notification 或 Link Intent。
功能說明:身分鍵與資料庫語意
永久識別鍵由三部分構成:
| 欄位 | 用途 | 上限 |
|---|---|---|
type | 區分 oidc、saml、ldap 等協定語意 | 50 |
provider | IdP 的穩定識別值;OIDC 使用 exact issuer | 500 |
subject | IdP 在該 provider 下簽發的不可變 subject | 255 |
三個原始值一律區分大小寫,不做 lowercase 或 Unicode normalization。資料庫以
identity_key 查詢:它是帶版本前綴、長度前綴的 SHA-256 小寫 hex;hash 命中後,應用仍會
以 Java 精確比對原始三欄。如此即使 DBA 選擇不分大小寫的 collation,身份語意仍不會跟著
改變。
所有 variant 都以 identity_key 全域唯一定位;同一外部身分不能在不同租戶重複連結。
tenant 由連結後的 Account 推導,不參與外部身分查詢鍵。
ExternalIdentity 是不可變連結:type、provider、subject、account 與 tenant 都沒有
更新路徑。重新綁定時應先 unlink,再 link,讓權限檢查與 audit 邊界保持清楚。
Schema 管理
external_identity 是一般 JPA Entity,完全服從專案既有的
spring.jpa.hibernate.ddl-auto:
ddl-auto | 行為 |
|---|---|
create/create-drop | 建立新表與唯一鍵 |
update | 缺表時建立;既有 schema 依 Hibernate 規則更新 |
validate | 只驗證;缺表或不相容時停止啟動 |
none | 不建立也不驗證,由 migration/DBA 負責 |
新資料庫可直接交由 Hibernate 建表。既有安裝若已有舊版 external_identity 資料,不能只靠
ddl-auto=update:新增的非空 identity_key 必須先以相同 v1 演算法回填,檢查 tenant 範圍內
的重複連結,再建立 NOT NULL 與唯一限制。建議採 expand/backfill/contract migration:
- 先加 nullable
identity_key。 - 以
ExternalIdentityKey#lookupKey()等價的 migration 程式逐筆回填。 - 找出相同 identity key 的全域衝突並由管理者裁決。
- 加上 NOT NULL、固定長度與唯一限制,再部署讀取新 key 的版本。
API 參考
Reference implementation 提供 Account 子資源:
| 方法 | 路徑 | Authority |
|---|---|---|
GET | /api/v1/accounts/{accountId}/external-identities | external_identity:read |
POST | /api/v1/accounts/{accountId}/external-identities | external_identity:write |
DELETE | /api/v1/accounts/{accountId}/external-identities/{linkId} | external_identity:write |
ExternalIdentityManagementService 是唯一寫入邊界;HTTP、SCIM、批次匯入與自動 mapping 都應
重用它,才能共同維持唯一性、禁止服務帳號使用互動身分與 audit 紀律。Reference 預設把兩個
authority 配給 tenant variant 的 USER,以及 tenantless variant 的 USER/ADMIN。
專案可修改 controller 路徑、DTO、角色配置與管理範圍。
Tenant variant 中,USER 只能管理目前 tenant context 範圍內的 Account;範圍外與不存在
一律回 404。SUPER_ADMIN 可跨租戶。Tenantless variant 採 application scope。
配置選項:自動 mapping 參考實作
第一次登入若尚未連結,resolver 會呼叫 ExternalIdentityAccountMappingPolicy。預設 policy
是保守的 verified-email 參考實作,而且預設關閉。它只有在以下條件全部成立時才自動連結:
enabled=true,且 issuer 與設定值完全相同。- IdP 的
email_verified(或指定 claim)為 true。 - email domain 在該 issuer 的明確 allow-list。
- 全平台只有一個 Account 使用該 email。
- 目標不是 service account。
「全平台只有一個」是刻意收緊的 fail-closed 條件:Account.email 仍可重複,但只要兩個租戶或
同一租戶有多個 Account 共用該 email,policy 就回 account-ambiguous,不會任選一筆 auto-link。
因此允許共用 email 的部署應維持此 fallback 關閉,或改以 employee ID、directory object ID
等真正唯一的 claim 實作自訂 mapping policy。
兩個 server variant 都使用 eligible-role allow-list:Account 至少須有一個角色,且所有已
指派角色都必須列在 eligible-roles 中。預設空清單,代表權限模型尚未細分前,不允許任何
Account 透過 email fallback 自動連結。
app:
security:
external-identity:
auto-link:
enabled: true
eligible-roles: []
trusted-email-providers:
- issuer: https://login.example.com
email-claim: email
email-verified-claim: email_verified
allowed-email-domains:
- example.com
權限模型完成後,只應列入可安全 auto-link 的低權限角色,例如
SCREENING_STAFF 或 LAB_TECH。清單採整份覆蓋,新增或調整角色時應一併重新檢視。
這套 policy 適合「企業 IdP 已保證 email 唯一且受控」的部署。若實際 mapping 應以 employee
ID、directory object ID、SCIM 資料或專案自己的規則判定,請提供自己的
ExternalIdentityAccountMappingPolicy bean;reference fallback 會因
@ConditionalOnMissingBean 自動退讓。Policy 只接收已驗證的 external identity;tenant 一律
由映射到的 Account 推導。
Policy 只能回 Link、NoMatch 或 Reject,實際寫入仍由 management service 執行。此設計
不依賴 Link Intent 或 notification;需要人工核准的專案可在管理 API 外自行建立工作流。
進階用法:採用、客製與移除
- 原樣採用:保留管理 API,維持 auto-link 關閉,先由管理員預先建立連結。
- 小幅客製:調整 trusted issuer/domain、claim 名稱、預設角色或 controller DTO。
- 替換 mapping:提供自訂
ExternalIdentityAccountMappingPolicy,不要繞過 management service 直接寫 repository。 - 移除:若專案沒有任何外部登入,移除
external-identity及依賴它的oidc-login,並移除externalidentityreference domain、設定 import 與 schema。
管理 audit 只記錄 link、Account、tenant、provider 與來源,不記錄 subject;正式環境也不應 在一般登入 log 輸出 token claims。