10 KiB
10 KiB
外部单点登录(External SSO)接入说明
功能概述
- 支持外部系统携带一次性 token 跳转本系统,实现免密单点登录。
- 去除了历史实现中的 payload 解密、nonce 强校验、自动建号与邮箱匹配等逻辑,所有账号均需在本地预先存在并保持映射关系。
- 前后端新增
/system/sso/verify校验能力,返回标准的AuthLoginRespVO令牌信息,并记录审计与登录日志。 - 通过
ExternalSsoClient抽象外部接口调用,可按需自定义实现或复用默认 HTTP 客户端封装。
关键组件
后端
ExternalSsoServiceImpl:单点登录主流程,实现参数校验、外部用户查询、本地账号匹配、令牌签发与日志记录。ExternalSsoStrategy:定义按来源系统拆分的策略接口,不同系统可实现自定义的拉取与匹配逻辑。DefaultExternalSsoStrategy:默认策略实现,复用配置化的 HTTP 客户端与匹配顺序,可按优先级被自定义策略覆盖。ExternalSsoClient:获取外部用户信息的接口抽象。DefaultExternalSsoClient:基于RestTemplate的默认实现,支持 Header/Query/Body 占位符渲染、重试、响应字段映射、代理配置。ExternalSsoClientConfiguration:通过@Configuration在缺省情况下注册DefaultExternalSsoClientBean,允许业务自定义覆盖。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。
调用流程
-
外部系统完成本地认证后,构造 URL:
{本系统域名}/#/externalsso?x-token={外部token}&target={回跳地址}并跳转,可选附带sourceSystem指定来源系统。 -
Vue 页面
ExternalSsoCallback解析查询参数,优先读取x-token(兼容历史的token参数)并校验是否存在;缺失时终止流程并提示错误。 -
前端调用
POST /system/sso/verify,请求体:{ "token": "外部系统颁发的 token", "targetUri": "/#/dashboard", // 可选 "sourceSystem": "partner-a" // 可选 } -
ExternalSsoServiceImpl#verifyToken执行以下步骤:- 校验功能开关与 token 有效性。
- 基于
sourceSystem选择匹配的ExternalSsoStrategy,若无匹配直接返回来源系统不支持。 - 通过策略触发
ExternalSsoClient拉取外部用户信息(默认调用配置的 HTTP 接口)。 - 按策略内定义的匹配顺序(默认外部 ID → 用户名 → 手机号)查找本地账号,未命中直接返回 "未找到匹配的本地用户"。
- 校验账号状态是否启用,签发 OAuth2 访问令牌并记录登录日志(类型
LOGIN_EXTERNAL_SSO)。 - 生成操作审计日志,记录外部响应摘要、映射账号、目标地址、来源系统等信息。
-
前端拿到
AuthLoginRespVO,通过authUtil.setToken/setTenantId持久化,随后跳转到整理后的targetUri(默认/)。 -
当流程出现异常(例如 token 缺失、外部接口失败、账号不存在)时,后端返回对应错误码,前端弹出提示并清除本地缓存。
下图为时序示意:
外部系统 -> 浏览器 -> 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。
- 关闭开关或缺少 token 时抛出
- 外部用户获取:
- 默认客户端会在
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自动注册,若需要接入其它协议,可在任意配置类中自定义:@Bean public ExternalSsoClient customExternalSsoClient(...) { return new MyExternalSsoClient(...); } -
默认实现要点:
- 按配置构造
RestTemplate,支持连接/读取超时、HTTP 代理、重试次数。 - 解析 JSON 响应,将指定字段映射到
ExternalSsoUserInfo,并保留原始 data 节点到attributes。 - 在解析失败、状态码异常时抛出带原始响应的
ExternalSsoClientException。
- 按配置构造
配置项参考
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或ExternalSsoClientBean,可扩展不同来源系统的对接方式。 - 建议为主要错误场景编写集成测试:token 缺失、映射缺失、来源系统不支持、外部接口超时、账号被禁用等。
- 外部系统回调前可先调用
/system/sso/verify联调接口验证配置是否正确,再接入正式流程。