1. 新增 iwork 同步用户组织信息接口
2. 修复错误设置版本信息在 zt-dependencies 的 bug
This commit is contained in:
194
docs/外部单点登录.md
Normal file
194
docs/外部单点登录.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# 外部单点登录(External SSO)接入说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
- 支持外部系统携带一次性 token 跳转本系统,实现免密单点登录。
|
||||
- 去除了历史实现中的 payload 解密、nonce 强校验、自动建号与邮箱匹配等逻辑,所有账号均需在本地预先存在并保持映射关系。
|
||||
- 前后端新增 `/system/sso/verify` 校验能力,返回标准的 `AuthLoginRespVO` 令牌信息,并记录审计与登录日志。
|
||||
- 通过 `ExternalSsoClient` 抽象外部接口调用,可按需自定义实现或复用默认 HTTP 客户端封装。
|
||||
|
||||
## 关键组件
|
||||
|
||||
### 后端
|
||||
|
||||
- `ExternalSsoServiceImpl`:单点登录主流程,实现参数校验、外部用户查询、本地账号匹配、令牌签发与日志记录。
|
||||
- `ExternalSsoStrategy`:定义按来源系统拆分的策略接口,不同系统可实现自定义的拉取与匹配逻辑。
|
||||
- `DefaultExternalSsoStrategy`:默认策略实现,复用配置化的 HTTP 客户端与匹配顺序,可按优先级被自定义策略覆盖。
|
||||
- `ExternalSsoClient`:获取外部用户信息的接口抽象。
|
||||
- `DefaultExternalSsoClient`:基于 `RestTemplate` 的默认实现,支持 Header/Query/Body 占位符渲染、重试、响应字段映射、代理配置。
|
||||
- `ExternalSsoClientConfiguration`:通过 `@Configuration` 在缺省情况下注册 `DefaultExternalSsoClient` Bean,允许业务自定义覆盖。
|
||||
- `ExternalSsoProperties`:`external-sso.*` 配置项,包含开关、外部接口、账号映射、跨域等子配置;示例配置已同步到 `zt-module-system/zt-module-system-server/src/main/resources/application.yaml` 与 `zt-server/src/main/resources/application.yaml`。
|
||||
- `ExternalSsoVerifyReqVO`:`POST /system/sso/verify` 请求载荷。
|
||||
- `ExternalSsoUserInfo`:外部用户标准化模型,包含基础字段与自定义属性集合。
|
||||
|
||||
### 前端
|
||||
|
||||
- `src/router/modules/remaining.ts`:新增隐藏路由 `/externalsso`,用于回调页面。
|
||||
- `src/views/Login/ExternalSsoCallback.vue`:处理 URL 参数、调用校验接口、落地令牌、跳转目标页面,异常时提示并引导返回登录页。
|
||||
- `src/api/login/index.ts`:新增 `externalSsoVerify` 方法,请求 `/system/sso/verify` 并返回 `TokenType`。
|
||||
|
||||
## 调用流程
|
||||
|
||||
1. 外部系统完成本地认证后,构造 URL:`{本系统域名}/#/externalsso?x-token={外部token}&target={回跳地址}` 并跳转,可选附带 `sourceSystem` 指定来源系统。
|
||||
2. Vue 页面 `ExternalSsoCallback` 解析查询参数,优先读取 `x-token`(兼容历史的 `token` 参数)并校验是否存在;缺失时终止流程并提示错误。
|
||||
3. 前端调用 `POST /system/sso/verify`,请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "外部系统颁发的 token",
|
||||
"targetUri": "/#/dashboard", // 可选
|
||||
"sourceSystem": "partner-a" // 可选
|
||||
}
|
||||
```
|
||||
|
||||
4. `ExternalSsoServiceImpl#verifyToken` 执行以下步骤:
|
||||
|
||||
- 校验功能开关与 token 有效性。
|
||||
- 基于 `sourceSystem` 选择匹配的 `ExternalSsoStrategy`,若无匹配直接返回来源系统不支持。
|
||||
- 通过策略触发 `ExternalSsoClient` 拉取外部用户信息(默认调用配置的 HTTP 接口)。
|
||||
- 按策略内定义的匹配顺序(默认外部 ID → 用户名 → 手机号)查找本地账号,未命中直接返回 "未找到匹配的本地用户"。
|
||||
- 校验账号状态是否启用,签发 OAuth2 访问令牌并记录登录日志(类型 `LOGIN_EXTERNAL_SSO`)。
|
||||
- 生成操作审计日志,记录外部响应摘要、映射账号、目标地址、来源系统等信息。
|
||||
|
||||
5. 前端拿到 `AuthLoginRespVO`,通过 `authUtil.setToken`/`setTenantId` 持久化,随后跳转到整理后的 `targetUri`(默认 `/`)。
|
||||
6. 当流程出现异常(例如 token 缺失、外部接口失败、账号不存在)时,后端返回对应错误码,前端弹出提示并清除本地缓存。
|
||||
|
||||
下图为时序示意:
|
||||
|
||||
```text
|
||||
外部系统 -> 浏览器 -> Vue /externalsso -> POST /system/sso/verify -> ExternalSsoService -> ExternalSsoClient -> 外部用户接口
|
||||
\-> OAuth2TokenService -> 登录日志/审计日志
|
||||
```
|
||||
|
||||
## 前端细节
|
||||
|
||||
- `ExternalSsoCallback.vue` 对 `target`/`targetUri`/`redirect` 参数做统一解码与归一化,支持携带绝对地址或 hash 路由,防止开放跳转漏洞。
|
||||
- 解析 URL 中的 `sourceSystem`(兼容 `source`、`systemCode`)并透传给后端,便于在多来源系统场景下选择策略。
|
||||
- 在成功回调后调用 `router.replace`,保证不会产生历史记录;失败时引导用户回到 `/login` 并附带原目标地址。
|
||||
- 通过 `buildErrorMessage` 兼容后端返回的 `msg`、`Error` 对象或字符串,统一展示错误提示。
|
||||
|
||||
## 后端流程拆解
|
||||
|
||||
- **开关与参数校验**:
|
||||
- 关闭开关或缺少 token 时抛出 `EXTERNAL_SSO_DISABLED` / `EXTERNAL_SSO_TOKEN_MISSING`。
|
||||
- **外部用户获取**:
|
||||
- 默认客户端会在 `external-sso.remote` 中加载请求配置,支持 GET/POST 等多种场景。
|
||||
- 占位符:`${externalUserId}`(初始值为 token)、`${shareToken}`/`${token}`(共享服务访问 token)、`${xToken}`(原始回调 token)、`${targetUri}`、`${sourceSystem}`。
|
||||
- 会自动通过 `ShareServiceUtils` 获取共享服务访问 token,并写入 `ShareServiceProperties#tokenHeaderName` 对应的请求头。
|
||||
- 请求体会以 JSON 形式发送 `{ "x-token": "回调参数中的 token" }`,满足上游接口 `S_BF_CS_01` 的要求。
|
||||
- `validateResponse` 可按 `codeField` 与 `successCode` 校验业务状态,失败时抛出带详细信息的 `ExternalSsoClientException`。
|
||||
- **本地账号匹配**:
|
||||
- 使用 `mapping.order` 控制字段优先级;`custom.entries` 保存 "外部ID → 本地用户ID" 静态映射。
|
||||
- 找不到用户或账号禁用时分别抛出 `EXTERNAL_SSO_USER_NOT_FOUND`、`EXTERNAL_SSO_USER_DISABLED`,均会同步写入登录日志。
|
||||
- **令牌签发与日志**:
|
||||
- 通过 `OAuth2TokenService#createAccessToken` 使用默认客户端 `CLIENT_ID_DEFAULT` 颁发本地访问令牌。
|
||||
- `recordAuditLog` 把原始响应的 SHA-256 摘要、外部属性、token 摘要等写入操作日志,便于排查。
|
||||
- `recordLoginLog` 记录登录行为并在成功时更新用户最后登录 IP。
|
||||
|
||||
## `ExternalSsoClient` 扩展
|
||||
|
||||
- 默认实现 `DefaultExternalSsoClient` 由 `ExternalSsoClientConfiguration` 自动注册,若需要接入其它协议,可在任意配置类中自定义:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public ExternalSsoClient customExternalSsoClient(...) {
|
||||
return new MyExternalSsoClient(...);
|
||||
}
|
||||
```
|
||||
|
||||
- 默认实现要点:
|
||||
- 按配置构造 `RestTemplate`,支持连接/读取超时、HTTP 代理、重试次数。
|
||||
- 解析 JSON 响应,将指定字段映射到 `ExternalSsoUserInfo`,并保留原始 data 节点到 `attributes`。
|
||||
- 在解析失败、状态码异常时抛出带原始响应的 `ExternalSsoClientException`。
|
||||
|
||||
## 配置项参考
|
||||
|
||||
```yaml
|
||||
external-sso:
|
||||
enabled: true
|
||||
system-code: example-partner
|
||||
token:
|
||||
secret: "shared-secret"
|
||||
algorithm: AES
|
||||
allowed-clock-skew-seconds: 60
|
||||
max-age-seconds: 300
|
||||
require-nonce: false
|
||||
replay-protection-enabled: false
|
||||
remote:
|
||||
base-url: http://10.1.7.110
|
||||
user-info-path: /api/sso/user
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer ${token}"
|
||||
query-params: {}
|
||||
body:
|
||||
userId: "${externalUserId}"
|
||||
code-field: code
|
||||
success-code: "0"
|
||||
message-field: message
|
||||
data-field: data
|
||||
user-id-field: data.userId
|
||||
username-field: data.username
|
||||
nickname-field: data.nickname
|
||||
email-field: data.email
|
||||
mobile-field: data.mobile
|
||||
tenant-id-field: data.tenantId
|
||||
connect-timeout-millis: 3000
|
||||
read-timeout-millis: 5000
|
||||
retry-count: 1
|
||||
proxy:
|
||||
enabled: false
|
||||
mapping:
|
||||
order:
|
||||
- EXTERNAL_ID
|
||||
- USERNAME
|
||||
- MOBILE
|
||||
ignore-case: true
|
||||
update-profile-on-login: false
|
||||
custom:
|
||||
entries:
|
||||
partnerUser001: 10001
|
||||
cors:
|
||||
allowed-origins:
|
||||
- https://partner.example.com
|
||||
allowed-methods: ["OPTIONS", "POST"]
|
||||
allowed-headers: ["Authorization", "Content-Type"]
|
||||
allow-credentials: true
|
||||
max-age: 1800
|
||||
```
|
||||
|
||||
| 配置路径 | 说明 |
|
||||
| --- | --- |
|
||||
| `enabled` | 总开关,关闭后接口直接返回 `EXTERNAL_SSO_DISABLED` |
|
||||
| `system-code` | 默认来源系统标识,可作为 `sourceSystem` 的缺省值及日志标签 |
|
||||
| `token.*` | 若仍需解密/校验外部 token,可在自定义 `ExternalSsoClient` 内按需使用;默认实现仅透传 |
|
||||
| `remote.*` | 外部接口 HTTP 调用参数、字段映射与超时控制,模板占位符支持 `externalUserId`、`shareToken`(`token`)、`xToken`、`targetUri`、`sourceSystem` |
|
||||
| `mapping.order` | 本地账号匹配优先级,支持 `EXTERNAL_ID`、`USERNAME`、`MOBILE` |
|
||||
| `mapping.custom.entries` | 外部用户标识到本地用户 ID 的静态映射表 |
|
||||
| `cors.*` | 用于开放 `/system/sso/verify` 的跨域访问白名单 |
|
||||
|
||||
## 错误码与日志
|
||||
|
||||
- 错误码:
|
||||
- `1_002_000_050`:功能未开启。
|
||||
- `1_002_000_051`:token 缺失。
|
||||
- `1_002_000_055`:外部接口异常,具体原因写入占位符。
|
||||
- `1_002_000_056`:未匹配到本地用户。
|
||||
- `1_002_000_057`:本地用户已禁用。
|
||||
- `1_002_000_058`:来源系统不支持,需配置匹配的策略实现。
|
||||
- 登录日志:使用 `LoginLogTypeEnum.LOGIN_EXTERNAL_SSO` 记录成功/失败。
|
||||
- 操作日志:类型 `EXTERNAL_SSO/VERIFY`,包含外部用户 ID、映射账号、目标地址、来源系统、响应摘要等元数据。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 所有账号须提前维护映射,系统不会自动创建或按邮箱兜底匹配用户。
|
||||
- `targetUri` 会在前端归一化,避免开放跳转风险;无合法目标时默认跳转首页。
|
||||
- 确保外部接口返回的 JSON 字段与配置保持一致,必要时可通过 `remote.data-field` 指向具体节点。
|
||||
- 若外部接口速度较慢或易失败,可提高 `retry-count`、超时时间或自定义客户端实现。
|
||||
- 如需记录更多审计信息,可在 `ExternalSsoUserInfo#addAttribute` 注入自定义字段,审计日志会自动保留。
|
||||
|
||||
## 扩展与测试建议
|
||||
|
||||
- 通过提供新的 `ExternalSsoStrategy` 或 `ExternalSsoClient` Bean,可扩展不同来源系统的对接方式。
|
||||
- 建议为主要错误场景编写集成测试:token 缺失、映射缺失、来源系统不支持、外部接口超时、账号被禁用等。
|
||||
- 外部系统回调前可先调用 `/system/sso/verify` 联调接口验证配置是否正确,再接入正式流程。
|
||||
Reference in New Issue
Block a user