参考文章:
https://blog.youkuaiyun.com/qq_45780736/article/details/153470855?spm=1001.2014.3001.5506
以下是 A、B 系统跨子域单点登录(SSO)状态同步的完整实现方案,以清晰的文字流程描述,适用于前端团队协作或文档编写。
🎯 目标
实现:
当用户在 A 系统退出登录 后,跳转到 B 系统 时,B 系统能自动感知会话已失效,并触发登录态检查,避免“假登录”状态。
✅ 前提:A 和 B 同主域,如
a.company.com和b.company.com
🧩 核心机制
采用 “时间线矛盾检测 + 用户切换检测”双保险机制:
loginTime:记录本次会话的登录时间(由各系统本地维护)logoutTime:记录全局登出时间(通过跨子域 Cookie 共享)userId:用于检测用户是否切换账号
B 系统在路由守卫中判断:
- 是否从外部跳转?
- 若是,则检查:
- 用户 ID 是否变化?
- 登出时间是否晚于登录时间?
任一为真 → 强制调用 checkLogin 接口验证真实登录态。
🔧 完整实现流程
一、A 系统:登录逻辑
- 用户登录成功后,执行以下操作:
- 记录本次登录时间到
localStorage(供 B 系统参考):localStorage.setItem('sso-login-time', Date.now()); localStorage.setItem('sso-user-id', userId); - (可选)将
loginTime存入 Cookie(若 B 无法读localStorage):document.cookie = `sso-login-time=${Date.now()}; domain=.company.com; path=/; Secure; SameSite=None`;
- 记录本次登录时间到
二、A 系统:登出逻辑
- 用户点击“退出”:
-
第一步:先写入“全局登出时间”到跨子域 Cookie:
const logoutTime = Date.now(); document.cookie = `sso-logout-timestamp=${logoutTime}; domain=.company.com; path=/; Secure; SameSite=None`;✅ 关键:必须在清除其他 Cookie 之前 写入!
-
第二步:调用后端
/api/logout接口,清除服务端会话(Session)。 -
第三步:清除 A 系统自身的业务 Cookie(如 token、临时数据),但不删除
sso-logout-timestamp。 -
第四步:跳转到目标系统(如 B)或登录页:
window.location.href = 'https://b.company.com/home?from=a-system&userId=123&t=' + Date.now();
-
三、B 系统:路由守卫逻辑
在页面加载或路由跳转前执行以下判断:
1. 判断是否从外部系统跳转
- 检查 URL 参数
from:- 若
from存在且不为b-system,则标记为 外部跳转。
- 若
2. 获取关键时间与用户信息
- 本地登录时间
localLoginTime:- 优先从
localStorage.getItem('sso-login-time')读取。 - 若无,可尝试从 Cookie 读取。
- 优先从
- 全局登出时间
globalLogoutTime:- 从 Cookie 读取:
getCookie('sso-logout-timestamp')
- 从 Cookie 读取:
- 本地用户 ID
localUserId:- 从
localStorage或缓存中获取。
- 从
- 来源用户 ID
fromUserId:- 从 URL 参数
userId获取。
- 从 URL 参数
3. 判定是否需要强制检查登录态
满足以下任一条件,即触发 forceCheckLogin():
| 条件 | 说明 |
|---|---|
isExternal | 从外部跳转,保守策略,需验证 |
fromUserId && localUserId && fromUserId !== localUserId | 用户已切换账号 |
globalLogoutTime && localLoginTime && +globalLogoutTime > +localLoginTime | 会话已注销(登出晚于登录) |
4. 执行动作
- 若需检查 → 调用
/api/user/info接口验证真实登录态。 - 若无需检查 → 使用本地缓存的
userInfo。
📦 数据存储说明
| 数据 | 存储位置 | 作用域 | 是否可清除 |
|---|---|---|---|
sso-login-time | localStorage 或 Cookie | 各系统独立 | 可清除(登录时覆盖) |
sso-logout-timestamp | Cookie(domain=.company.com) | 跨子域共享 | ❌ 不可清除(登出时写入后保留) |
sso-user-id | localStorage | 本地维护 | 可清除 |
⚠️ 注意:
sso-logout-timestamp的 Cookie 不能被清除,否则 B 无法感知登出事件。
🧪 示例场景
T1: 用户在 a.company.com 登录 → 记录 loginTime = 1729000000000
T2: 用户在 A 退出:
- 写入 cookie: sso-logout-timestamp = 1729000500000
- 跳转 https://b.company.com/home?from=a-system&userId=1001
T3: B 系统加载:
- 检测到 from=a-system → 外部跳转
- 读取:
- localLoginTime = 1729000000000
- globalLogoutTime = 1729000500000
- 判断:1729000500000 > 1729000000000 → 成立
- 触发 forceCheckLogin()
T4: /api/user/info 返回 401 或无用户 → 跳转登录页
✅ 优势总结
- 前端可控:无需后端广播登出事件
- 无需 iframe:规避安全策略限制
- 可靠:基于时间线矛盾,逻辑严谨
- 兼容性好:支持主流浏览器
- 可扩展:可加入
BroadcastChannel实现同域标签页同步
📝 注意事项
- 时间同步:确保前后端时间误差在可接受范围内(一般 ±1 分钟内无问题)。
- Cookie 配置:
domain=.company.com、Secure、SameSite=None(如需跨站)。 - 防缓存:跳转时加
t=时间戳参数,避免浏览器缓存页面。 - 清理策略:
sso-logout-timestamp可设置较短有效期(如 30 分钟),避免长期残留。
🏁 结语
该方案通过 “登出时间 > 登录时间” 的时间线矛盾检测,实现了跨系统登录态的最终一致性,有效解决了“A 退出、B 仍显示登录”的常见 SSO 问题,适合在无统一认证中心(UMS)或 iframe 受限的场景下使用
96

被折叠的 条评论
为什么被折叠?



