Files
zt-dsc/docs/外部单点登录.md
chenbowen 0b646295da 1. 新增 iwork 同步用户组织信息接口
2. 修复错误设置版本信息在 zt-dependencies 的 bug
2025-11-20 18:27:01 +08:00

195 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 外部单点登录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` 联调接口验证配置是否正确,再接入正式流程。