一、单点登录(SSO)是什么?
核心定义
单点登录(Single Sign-On,SSO)是一种身份认证解决方案,允许用户通过一次登录访问多个相互信任的应用系统。其核心逻辑是统一认证中心与分布式会话管理,避免用户重复输入凭证,提升跨系统访问效率。
技术原理
- 独立认证中心(Passport)
所有子系统的登录请求均由认证中心处理,子系统本身不存储用户凭证。认证中心负责验证用户身份,并颁发全局唯一的令牌(Token)或会话标识(Session ID)。 - 令牌传递与会话共享
- 认证中心登录成功后,生成令牌并通过安全方式(如 Cookie、Header)传递给子系统。
- 子系统通过令牌向认证中心验证用户身份,验证通过后建立局部会话(存储用户权限等信息),实现免密访问。
- 跨系统会话一致性
- 全局会话:用户与认证中心的会话,标识用户整体登录状态。
- 局部会话:用户与单个子系统的会话,依赖全局会话存在。
- 约束关系:全局会话销毁时,所有局部会话需同步销毁;局部会话存在时,全局会话必然存在。
经典案例:电商平台跨应用登录
以 React 电商中台系统 为例:
-
主站(
main.com
)使用 React 开发,管理后台(admin.com
)使用 Vue3 开发,两者域名不同。 -
用户在主站登录后,认证中心(
sso.com
)生成 Token,并通过以下方式实现跨域共享:
- 主站将 Token 存储于 LocalStorage,并通过
postMessage
传递给管理后台的 iframe 页面,写入其 LocalStorage。 - 管理后台前端每次请求时携带 Token,后端验证通过后建立局部会话,用户无需重复登录。
- 主站将 Token 存储于 LocalStorage,并通过
二、如何实现单点登录?
场景一:同域名下的单点登录(子域共享 Cookie)
适用场景:同一主域名下的子系统(如 shop.xxx.com
和 admin.xxx.com
)。
实现原理:利用 Cookie 的 domain 和 path 属性跨子域共享会话标识。
步骤(以 Vue3 为例):
-
设置认证中心 Cookie
在认证中心登录成功后,将 Session ID 写入 Cookie,配置:document.cookie = `sessionId=xxx; domain=.xxx.com; path=/; secure; HttpOnly`;
domain=.xxx.com
:允许所有子域访问该 Cookie。path=/
:根路径下所有资源均可读取。
-
子系统验证会话
子系统(如 Vue3 应用)在路由守卫中检查 Cookie 中的sessionId
:// Vue3 路由守卫 router.beforeEach((to, from, next) => {const sessionId = document.cookie.match(/sessionId=([^;]+)/)?.[1];if (to.meta.requiresAuth && !sessionId) {next(`/sso/login?redirect=${encodeURIComponent(to.fullPath)}`); // 跳转认证中心} else {next();} });
优势:实现简单,无需后端复杂逻辑;局限:仅适用于同主域名场景。
场景二:不同域名下的单点登录(标准 SSO 方案)
适用场景:跨域名系统(如 react-app.com
与 vue-app.com
)。
核心方案:基于 认证中心 + 令牌校验 的分布式架构。
方案一:后端主导的重定向认证(通用方案)
技术栈:认证中心(Node.js/Java) + 子系统(React/Vue3)。
流程步骤:
-
用户访问子系统(React 应用)
子系统检测到未登录,重定向至认证中心,并携带回调地址:// React 前端重定向 window.location.href = `https://sso.com/login?redirect=https://react-app.com/dashboard`;
-
认证中心处理登录
-
用户输入凭证,认证中心验证通过后生成 JWT 令牌,并存储用户会话(如 Redis)。
-
重定向回子系统,附带令牌参数:
// 认证中心 Node.js 后端 app.get('/login', (req, res) => {// 验证用户逻辑...const token = generateJWT(user.id);res.redirect(`${req.query.redirect}?token=${token}`); });
-
-
子系统校验令牌
子系统(Vue3 应用)后端接收令牌,调用认证中心接口验证合法性:// Vue3 后端(Node.js) app.get('/dashboard', async (req, res) => {const token = req.query.token;const isValid = await fetch(`https://sso.com/verify-token?token=${token}`); // 校验令牌if (isValid) {// 创建局部会话(如设置子系统 Cookie)res.cookie('localSession', 'valid', { domain: 'vue-app.com' });res.send('受保护资源');} else {res.redirect('/login');} });
优势:安全性高,支持跨域;劣势:需要后端深度参与,流程较复杂。
方案二:前端主导的 LocalStorage 共享(轻量级方案)
技术栈:纯前端(React + Vue3) + 认证中心 API。
核心逻辑:通过 iframe
+ postMessage
跨域传递 Token 至各系统的 LocalStorage。
实现步骤(以 React 为例):
-
认证中心返回 Token 给主系统
// React 主系统登录回调 const handleLogin = async () => {const response = await fetch('https://sso.com/login', { method: 'POST', body: formData });const { token } = await response.json();// 存储主系统 TokenlocalStorage.setItem('mainToken', token);// 通过 iframe 向 Vue3 子系统传递 Tokenconst iframe = document.createElement('iframe');iframe.src = 'https://vue-app.com/sso-helper'; // Vue3 子系统的跨域接收页面document.body.appendChild(iframe);setTimeout(() => {iframe.contentWindow.postMessage(token, 'https://vue-app.com'); // 安全限制目标域名}, 1000); };
-
Vue3 子系统接收 Token
创建sso-helper.html
页面(Vue3 项目静态文件),监听跨域消息并存储 Token:<!-- Vue3 子系统 sso-helper.html --> <script>window.addEventListener('message', (event) => {if (event.origin === 'https://react-app.com') { // 验证来源域名localStorage.setItem('vueToken', event.data);}}); </script>
-
各系统请求携带 Token
前端每次发请求时从 LocalStorage 读取 Token 并添加到请求头:// React 全局 Axios 拦截器 axios.interceptors.request.use(config => {config.headers.Authorization = `Bearer ${localStorage.getItem('mainToken')}`;return config; });
优势:后端无侵入,部署灵活;风险:依赖前端安全性,需严格校验消息来源域名。
三、单点登录核心流程与代码示例
流程时序图
关键代码片段(跨域场景)
-
认证中心生成 Token(Node.js)
const jwt = require('jsonwebtoken'); app.post('/login', (req, res) => {const { username, password } = req.body;// 验证用户逻辑...const token = jwt.sign({ userId: '123' }, 'sso-secret-key', { expiresIn: '1h' });res.json({ token }); // 返回给前端,由前端处理跨域传递 });
-
React 前端跨域传递 Token 至 Vue3
// React 登录成功后 const sendTokenToVueApp = (token) => {const iframe = document.createElement('iframe');iframe.style.display = 'none';iframe.src = 'https://vue-app.com/sso-iframe'; // Vue3 接收页面document.body.appendChild(iframe);iframe.onload = () => {iframe.contentWindow.postMessage({ type: 'SSO_TOKEN', token },'https://vue-app.com');}; };
-
Vue3 前端监听跨域消息
// Vue3 主文件 main.js mounted() {window.addEventListener('message', this.handleSSOMessage); }, methods: {handleSSOMessage(event) {if (event.origin === 'https://react-app.com' && event.data.type === 'SSO_TOKEN') {localStorage.setItem('ssoToken', event.data.token);this.$router.push('/dashboard'); // 自动跳转已登录页面}} }
四、常见问题与解决方案
- 跨域 Cookie 共享限制
- 同域名场景通过
domain
属性解决,不同域名需依赖认证中心重定向或前端跨域通信(如postMessage
)。
- 同域名场景通过
- 令牌安全性
- 使用 HTTPS 传输 Token,避免明文泄露;
- 令牌设置短有效期(如 1 小时),搭配刷新令牌(Refresh Token)机制续期。
- 单点登出(SLO)
- 全局登出时,认证中心需销毁全局会话,并通知所有子系统销毁局部会话(可通过广播消息或子系统主动校验令牌失效)。
- 跨域 Cookie 共享限制
五、技术选型总结
场景 | 推荐方案 | 技术栈示例 | 优势 |
---|---|---|---|
同域名子系统 | Cookie 共享 | React + Express | 简单高效,后端轻量级 |
跨域名企业系统 | 认证中心 + 重定向校验 | Vue3 + Spring Boot + Redis | 安全性高,符合企业级规范 |
纯前端跨域应用 | LocalStorage + postMessage | React + Vue3 + Node.js | 前端主导,后端无侵入 |