背景
我们为上气发提供了一套质量数据分析系统,以下简称 SPC。上气发希望在已有的企业门户中增加 SPC 入口,让员工登录门户后,点击入口就能进入我们的系统,不必再次输入账号密码。本次采用 LTPA 单点登录方式,下面记录对接过程。
1. 加一个链接,为什么还不够?
门户和 SPC 是两个独立的系统。员工登录了门户,不代表 SPC 就知道他是谁。如果只在门户放一个 SPC 首页链接,打开后仍然会遇到 SPC 的登录页。
要省掉第二次登录,就需要让 SPC 能确认:这个人确实已经通过企业认证,而且知道他对应 SPC 中的哪个用户。
门户负责提供入口,企业认证服务负责确认身份,我们的系统负责找到对应账户,并决定用户进入后可以使用哪些功能。
2. LTPA 在这里起什么作用?
员工登录门户后,浏览器会保存一份 LTPA 登录凭据。本次这份凭据放在名为 LtpaToken 的 Cookie 中。Cookie 可以理解为浏览器保存的一小份数据,访问符合条件的地址时,会随请求一起发送过去。
员工从门户打开 SPC 时,浏览器带上这份凭据。SPC 不凭“收到了凭据”就放行,而是交给企业提供的认证服务验证。这个服务在本项目中叫 NAUTHM。
验证成功后,认证服务会告诉我们:这份凭据属于哪一个员工。员工不必再输入密码,SPC 也不需要获取他的门户密码。
这里有一个前提:**门户 Cookie 的适用范围必须覆盖 SPC 的地址,并满足浏览器的发送条件。**否则,SPC 收不到凭据,后面的认证就无法开始。点击链接本身不会把两个系统的登录状态打通。
3. 为什么还要先同步人员?
知道“这是企业中的某位员工”,还不足以让他使用 SPC。我们的系统需要有他的账户,才能给他分配功能权限,并记录是谁录入了数据、处理了异常。
因此,启用单点登录前,我们从企业人员目录中同步员工及部门信息,为员工建立 SPC 账户,并保存双方人员编号的对应关系。这个企业人员目录在本项目中叫 ODM。
举个例子:某位员工的企业人员编号是 10001,同步后对应的 SPC 用户编号是 20001。认证服务告诉我们“当前访问者是员工 10001”,SPC 就能找到自己的用户 20001,并读取他的权限。数字仅用于举例。
人员同步解决“有没有这个人”,登录认证解决“当前访问的是不是这个人”,权限管理解决“他能做什么”。
本次不在登录时临时创建账户,也不直接采用企业认证服务返回的角色。未同步的员工不能登录,功能权限由 SPC 管理员分配。
4. 员工点击入口后,发生了什么?
假设员工已经登录门户、已同步到 SPC,且浏览器可以发送门户凭据,一次成功登录的过程如下:
员工点击门户中的 SPC 入口,浏览器在新页签打开我们的单点登录地址。
浏览器随请求带上
LtpaToken,SPC 后端读取这份凭据。SPC 后端向企业认证服务验证凭据,获得员工编号。
SPC 找到对应的本地账户,检查账户是否启用,并读取其功能权限。
SPC 保存自己的登录状态,让浏览器进入系统首页。
sequenceDiagram
actor U as 员工
participant B as 浏览器
participant S as 我们的 SPC 系统
participant N as 企业认证服务
U->>B: 登录门户,点击 SPC 入口
B->>S: 打开登录入口,携带 LTPA 凭据
S->>N: 这份凭据有效吗?属于谁?
N-->>S: 验证成功,返回员工编号
S->>S: 找到本地账户,检查状态和权限
S-->>B: 保存 SPC 登录状态,跳转首页
最后一步为什么还要保存 SPC 登录状态?因为员工进入系统后,还会查看图表、录入数据。我们需要记住他已经登录,不必每打开一个页面就向企业认证服务重新验证。
本项目把这份登录状态保存在服务端,称为 Session(会话),同时向浏览器设置名为 DT_SESSION 的 Cookie。浏览器以后访问 SPC 时带上它,我们就能找到对应的登录状态。
所以,两份 Cookie 用途不同:LtpaToken 用于验证门户身份,DT_SESSION 用于后续访问 SPC。前端页面不需要自行读取或转存它们。
5. 实际对接怎么做?
上气发侧提供认证接口、调用接口所需的应用凭证,以及组织和人员目录接口,并配置门户中的 SPC 入口。我们实现人员同步、单点登录入口、身份验证和本地登录状态保存。
实施顺序是:先确认接口和 Cookie 条件,再同步人员、配置权限,随后开发登录入口,最后从真实门户点击入口联调。下面是本次实现中需要注意的接口细节。
两个地址,用途不同
门户中的链接指向我们的 /api/auth/sso/login。浏览器访问这个入口,先完成登录,再进入首页,不能只配置一个普通的首页链接。
我们的后端收到凭据后,再调用企业认证服务的 POST /account/login,请求体如下:
{
"token": "<从 Cookie 读取的 LtpaToken>",
"hasCompanyInfo": true,
"hasAuthInfo": true
}
调用时还要携带两个请求头:
X-Sec-App-Auth-Key:应用鉴权密钥。Authorization:按本次约定使用Basic Base64(appCode:apiAccount:apiPassword)。
应用凭证证明“我们的系统有权调用接口”,请求体中的 LtpaToken 用于确认“当前员工是谁”。接口地址和凭证通过后端环境配置提供,不交给前端。
返回成功后,找对员工编号
请求成功不代表认证成功。本次要求 HTTP 状态为 2xx,且响应中的 code == 200、isError == false、account.userId 有值:
{
"code": 200,
"isError": false,
"account": { "userId": 10001 }
}
双方已确认,account.userId 对应人员目录中的员工 id。我们用这个编号查找之前同步的账户,不用姓名、邮箱猜测匹配,也不能误用另一个相似字段 account.id。
这些接口地址、请求头和字段是本次企业接口的约定,不是所有 LTPA 对接都使用这一套接口。
保存登录状态,再进入首页
找到可用账户后,我们复用原有登录机制保存会话,并设置 DT_SESSION,同时返回 302 响应,通知浏览器跳转到 /spc 首页。
浏览器保存 Cookie 后跟随跳转,页面通过 /api/auth/me 查询当前登录用户。后续仍使用原来的用户和权限机制,不必为门户用户另写一套业务逻辑。
6. 联调时怎么排查?
按登录顺序检查,比只看“有没有进入首页”更容易定位问题:
还要检查失败提示和原有账号密码登录。本次登录失败会返回登录页并显示原因,保留密码登录入口,不自动反复跳转重试。
7. 结果与边界
本次对接已完成:已同步且启用的员工可以从上气发门户进入我们的 SPC 系统,无需重复输入密码。系统仍保留本地账户、功能权限和账号密码登录。
登录凭据和密码不放进 URL 或日志,环境凭证不写入源码。登录成功时更换已有会话编号,设置会话 Cookie 的安全属性,禁止缓存登录响应,只允许跳转固定的首页或登录页。
另外,本次只实现单点登录,没有实现统一退出。退出 SPC 只结束 SPC 的登录状态,不会同时退出门户;如果门户凭据仍有效,再次点击入口仍可能重新登录。
总结起来,我们不是把门户和 SPC 合并成一个系统,而是让 SPC 能验证门户中的员工身份,找到自己的账户,再按自己的权限规则提供服务。