跳至主要内容

外部身分連結

概述

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區分 oidcsamlldap 等協定語意50
providerIdP 的穩定識別值;OIDC 使用 exact issuer500
subjectIdP 在該 provider 下簽發的不可變 subject255

三個原始值一律區分大小寫,不做 lowercase 或 Unicode normalization。資料庫以 identity_key 查詢:它是帶版本前綴、長度前綴的 SHA-256 小寫 hex;hash 命中後,應用仍會 以 Java 精確比對原始三欄。如此即使 DBA 選擇不分大小寫的 collation,身份語意仍不會跟著 改變。

所有 variant 都以 identity_key 全域唯一定位;同一外部身分不能在不同租戶重複連結。 tenant 由連結後的 Account 推導,不參與外部身分查詢鍵。

ExternalIdentity 是不可變連結:typeprovidersubjectaccount 與 tenant 都沒有 更新路徑。重新綁定時應先 unlink,再 link,讓權限檢查與 audit 邊界保持清楚。

Schema 管理

external_identity 是一般 JPA Entity,完全服從專案既有的 spring.jpa.hibernate.ddl-auto

ddl-auto行為
createcreate-drop建立新表與唯一鍵
update缺表時建立;既有 schema 依 Hibernate 規則更新
validate只驗證;缺表或不相容時停止啟動
none不建立也不驗證,由 migration/DBA 負責

新資料庫可直接交由 Hibernate 建表。既有安裝若已有舊版 external_identity 資料,不能只靠 ddl-auto=update:新增的非空 identity_key 必須先以相同 v1 演算法回填,檢查 tenant 範圍內 的重複連結,再建立 NOT NULL 與唯一限制。建議採 expand/backfill/contract migration:

  1. 先加 nullable identity_key
  2. ExternalIdentityKey#lookupKey() 等價的 migration 程式逐筆回填。
  3. 找出相同 identity key 的全域衝突並由管理者裁決。
  4. 加上 NOT NULL、固定長度與唯一限制,再部署讀取新 key 的版本。

API 參考

Reference implementation 提供 Account 子資源:

方法路徑Authority
GET/api/v1/accounts/{accountId}/external-identitiesexternal_identity:read
POST/api/v1/accounts/{accountId}/external-identitiesexternal_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 的 USERADMIN。 專案可修改 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_STAFFLAB_TECH。清單採整份覆蓋,新增或調整角色時應一併重新檢視。

這套 policy 適合「企業 IdP 已保證 email 唯一且受控」的部署。若實際 mapping 應以 employee ID、directory object ID、SCIM 資料或專案自己的規則判定,請提供自己的 ExternalIdentityAccountMappingPolicy bean;reference fallback 會因 @ConditionalOnMissingBean 自動退讓。Policy 只接收已驗證的 external identity;tenant 一律 由映射到的 Account 推導。

Policy 只能回 LinkNoMatchReject,實際寫入仍由 management service 執行。此設計 不依賴 Link Intent 或 notification;需要人工核准的專案可在管理 API 外自行建立工作流。

進階用法:採用、客製與移除

  • 原樣採用:保留管理 API,維持 auto-link 關閉,先由管理員預先建立連結。
  • 小幅客製:調整 trusted issuer/domain、claim 名稱、預設角色或 controller DTO。
  • 替換 mapping:提供自訂 ExternalIdentityAccountMappingPolicy,不要繞過 management service 直接寫 repository。
  • 移除:若專案沒有任何外部登入,移除 external-identity 及依賴它的 oidc-login,並移除 externalidentity reference domain、設定 import 與 schema。

管理 audit 只記錄 link、Account、tenant、provider 與來源,不記錄 subject;正式環境也不應 在一般登入 log 輸出 token claims。