前端单点登录
前端实现单点登录(SSO)
单点登录(Single Sign-On,简称 SSO)是一种认证机制,允许用户在多个系统之间只需登录一次,就可以访问所有相关系统,而不需要重复输入账号和密码。它的目标是提升用户体验,同时简化认证流程。
单点登录的基本原理
单点登录的核心思想是:用户登录后,认证信息会在多个系统之间共享,用户只需登录一次,就可以访问所有系统。
通常,单点登录的实现依赖于一个统一认证中心,它负责管理用户的登录状态和认证信息。前端的主要任务是与认证中心交互,并在多个系统之间共享登录状态。
单点登录的工作流程
- 用户访问系统 A
用户打开系统 A 的页面,前端检查用户是否已经登录。如果没有登录,前端会将用户重定向到认证中心。 - 用户登录认证中心
用户在认证中心输入账号和密码,认证中心验证用户信息。如果验证成功,认证中心会生成一个令牌(Token)或票据(Ticket),并将其返回给前端。 - 前端保存令牌并访问系统 A
前端拿到令牌后,将其保存(通常存储在cookie
或localStorage
中),并携带令牌向系统 A 发起请求。系统 A 会向认证中心验证令牌的有效性,验证通过后允许用户访问。 - 用户访问系统 B
用户从系统 A 跳转到系统 B,前端会检查用户的登录状态。如果发现用户已经登录(令牌有效),则直接允许访问系统 B;如果没有登录,则重定向到认证中心进行登录。
前端实现单点登录的关键点
1. 统一认证中心
单点登录的核心是认证中心,前端需要与认证中心交互。认证中心通常提供以下功能:
- 登录接口:用户输入账号密码后,认证中心返回令牌。
- 验证接口:系统 A 和系统 B 可以向认证中心验证令牌的有效性。
- 刷新接口:令牌过期后,前端可以通过认证中心刷新令牌。
2. 令牌的存储与传递
令牌是用户登录状态的凭证,前端需要妥善保存令牌,并在请求时携带令牌。常见的存储方式包括:
cookie
:将令牌存储在浏览器的cookie
中,设置为httpOnly
和secure
,可以提高安全性。localStorage
:将令牌存储在localStorage
中,适合单页面应用(SPA)。
3. 跨域问题
在单点登录中,认证中心和各个系统可能运行在不同的域名下(比如 auth.example.com
和 systemA.example.com
)。前端需要处理跨域问题,常见的解决方法包括:
- CORS:后端开启跨域资源共享(Cross-Origin Resource Sharing),允许前端跨域访问。
iframe
+postMessage
:通过嵌套iframe
和消息传递实现跨域通信。
实现单点登录的流程
以下是一个简单的单点登录实现流程:
1. 检查登录状态
当用户访问系统 A 时,前端会检查用户是否已经登录。如果没有登录,则重定向到认证中心。
function checkLogin() {const token = localStorage.getItem('userToken');if (!token) {// 如果没有令牌,重定向到认证中心window.location.href = 'https://auth.example.com/login?redirect=' + encodeURIComponent(window.location.href);} else {// 如果有令牌,验证令牌是否有效validateToken(token);}
}async function validateToken(token) {const response = await fetch('https://auth.example.com/validate', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ token }),});if (response.ok) {console.log('Token is valid');} else {// 如果令牌无效,重定向到认证中心window.location.href = 'https://auth.example.com/login?redirect=' + encodeURIComponent(window.location.href);}
}
2. 登录认证中心
用户在认证中心输入账号和密码,认证中心验证成功后返回令牌。
async function login(username, password) {const response = await fetch('https://auth.example.com/login', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ username, password }),});if (response.ok) {const data = await response.json();localStorage.setItem('userToken', data.token); // 保存令牌window.location.href = data.redirectUrl; // 跳转到原始页面} else {console.error('Login failed');}
}
3. 令牌验证与共享
当用户从系统 A 跳转到系统 B 时,系统 B 的前端会检查令牌,并向认证中心验证令牌的有效性。
function checkLoginForSystemB() {const token = localStorage.getItem('userToken');if (!token) {// 如果没有令牌,重定向到认证中心window.location.href = 'https://auth.example.com/login?redirect=' + encodeURIComponent(window.location.href);} else {// 验证令牌validateToken(token);}
}
跨域单点登录的解决方案
如果认证中心和系统 A/B 运行在不同的域名下,前端需要处理跨域问题。以下是两种常见的解决方案:
1. 使用 CORS
后端开启跨域资源共享,允许前端跨域访问认证中心的接口。
// 后端设置 CORS
app.use((req, res, next) => {res.header('Access-Control-Allow-Origin', '*');res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');next();
});
2. 使用 iframe
+ postMessage
通过嵌套 iframe
和消息传递实现跨域通信。
// 在认证中心页面中
window.addEventListener('message', (event) => {if (event.origin === 'https://systemA.example.com') {const token = 'userTokenFromAuthCenter';event.source.postMessage({ token }, event.origin);}
});// 在系统 A 页面中
const iframe = document.createElement('iframe');
iframe.src = 'https://auth.example.com';
document.body.appendChild(iframe);window.addEventListener('message', (event) => {if (event.origin === 'https://auth.example.com') {const { token } = event.data;localStorage.setItem('userToken', token);}
});
总结
单点登录的实现需要前端与认证中心紧密配合,主要包括以下步骤:
- 检查用户登录状态。
- 重定向到认证中心进行登录。
- 保存令牌并验证令牌的有效性。
- 处理跨域问题,确保令牌可以在多个系统之间共享。