SSO 单点登录
SSO 的原理和实现方式
问题
说一说 SSO 单点登录的原理和实现方式。
解答
SSO(Single Sign-On)单点登录是指用户只需登录一次,就可以访问多个相互信任的应用系统。
为什么需要 SSO
假设公司有多个系统:OA、CRM、邮箱等。没有 SSO 时,用户访问每个系统都要登录一次。有了 SSO,登录一次就能访问所有系统。
同域 SSO
当所有系统在同一主域名下(如 a.example.com、b.example.com),可以共享 Cookie。
// 登录成功后,设置 Cookie 到主域
document.cookie = 'token=xxx; domain=.example.com; path=/';
// 所有子域都能读取这个 Cookie
跨域 SSO(CAS 流程)
当系统在不同域名下,需要独立的认证中心(SSO Server)。
用户 -> 系统A -> SSO Server -> 系统A
| |
| 登录验证
| |
+<-- ticket --+
流程说明:
- 用户访问系统 A
- 系统 A 发现未登录,重定向到 SSO Server
- 用户在 SSO Server 登录
- SSO Server 生成 ticket,重定向回系统 A
- 系统 A 用 ticket 换取用户信息
- 用户访问系统 B 时,SSO Server 已有登录态,直接返回 ticket
前端实现示例
// 检查登录状态
function checkLogin() {
const token = localStorage.getItem('token');
if (!token) {
// 未登录,跳转 SSO
redirectToSSO();
}
}
// 跳转到 SSO 登录页
function redirectToSSO() {
const currentUrl = encodeURIComponent(window.location.href);
const ssoUrl = `https://sso.example.com/login?redirect=${currentUrl}`;
window.location.href = ssoUrl;
}
// SSO 回调处理(登录成功后跳回来)
function handleSSOCallback() {
const params = new URLSearchParams(window.location.search);
const ticket = params.get('ticket');
if (ticket) {
// 用 ticket 换取 token
fetch('/api/auth/validate', {
method: 'POST',
body: JSON.stringify({ ticket })
})
.then(res => res.json())
.then(data => {
localStorage.setItem('token', data.token);
// 清除 URL 中的 ticket
window.history.replaceState({}, '', window.location.pathname);
});
}
}
SSO Server 端简化示例
// Express 示例
const sessions = new Map(); // 存储登录态
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (validateUser(username, password)) {
const sessionId = generateId();
sessions.set(sessionId, { username, loginTime: Date.now() });
// 设置 SSO 域的 Cookie
res.cookie('sso_session', sessionId, { httpOnly: true });
// 生成 ticket 并重定向
const ticket = generateTicket(sessionId);
res.redirect(`${req.query.redirect}?ticket=${ticket}`);
}
});
// 验证 ticket 接口
app.post('/validate', (req, res) => {
const { ticket } = req.body;
const sessionId = validateTicket(ticket);
if (sessionId && sessions.has(sessionId)) {
const user = sessions.get(sessionId);
const token = generateJWT(user);
res.json({ token });
} else {
res.status(401).json({ error: 'Invalid ticket' });
}
});
单点登出
用户在一个系统登出时,需要通知所有系统清除登录态。
// 登出时通知 SSO Server
function logout() {
localStorage.removeItem('token');
// 跳转到 SSO 登出
window.location.href = 'https://sso.example.com/logout?redirect=' +
encodeURIComponent(window.location.origin);
}
关键点
- 同域 SSO:通过设置主域 Cookie 实现,简单直接
- 跨域 SSO:需要独立认证中心,通过 ticket 机制传递登录态
- ticket 一次性:ticket 只能使用一次,用后即废,防止重放攻击
- 安全考虑:ticket 要有过期时间,传输要用 HTTPS
- 单点登出:需要 SSO Server 通知所有已登录系统清除会话
目录