单点登录

单点登录
Breezli单点登录
概念
SSO,是一种身份验证机制,运行用户通过一次登录可以访问多个相关但独立的软件系统
一旦用户登录一个系统,所有相关系统都将识别该用户为已认证状态
实现
背景
认证中心:颁发令牌
应用A - 应用B:两个独立应用,共享同一个认证中心
技术栈
JS、Nodejs、OAuth 2.0协议
流程
前端部分
用户访问A
A检测登录状态 -> (未登录重定向认证中心)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 // 检查本地是否有有效的访问令牌
function checkLoginStatus() {
const token = localStorage.getItem('accessToken');
if (!token) {
// 如果没有令牌,重定向到认证中心
redirectToAuthServer();
} else {
console.log('用户已登录');
}
}
function redirectToAuthServer() {
const clientId = 'your-client-id'; // 应用的客户端ID
const redirectUri = encodeURIComponent('https://app-a.com/callback'); // 应用A的回调地址
const authUrl = `https://auth-server.com/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}`;
window.location.href = authUrl;
}在认证中心登录
认证中心生成认证码 + 重定向A
1
2
3
4
5 // 获取URL中的授权码
function getAuthorizationCode() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('code');
}A用授权码向认证中心换取访问令牌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 // 使用授权码换取访问令牌
async function exchangeToken(code) {
const response = await fetch('https://auth-server.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: 'your-client-id',
client_secret: 'your-client-secret',
redirect_uri: 'https://app-a.com/callback'
})
});
const data = await response.json();
const accessToken = data.access_token;
// 保存访问令牌
localStorage.setItem('accessToken', accessToken);
console.log('登录成功,访问令牌已保存');
}A保存令牌
用户访问B
B检查是否已登录 -> (未登录重定向认证中心)
1
2
3
4
5
6
7 >// 页面加载时检查授权码
>const code = getAuthorizationCode();
>if (code) {
>exchangeToken(code);
>} else {
>checkLoginStatus();
>}认证中心识别已登录,直接返回B并附带授权码
B用授权码向认证中心换取访问令牌
后端部分
认证中心登录页面
1
2
3
4
5
6 <!-- 登录页面 -->
<form action="/login" method="POST">
<input type="text" name="username" placeholder="用户名" required />
<input type="password" name="password" placeholder="密码" required />
<button type="submit">登录</button>
</form>认证中心接收到登录请求后,验证用户信息,并生成授权码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 app.post('/login', (req, res) => {
const { username, password } = req.body;
// 验证用户信息(这里仅作演示)
if (username === 'admin' && password === '123456') {
const code = generateAuthorizationCode(); // 生成随机授权码
const redirectUri = 'https://app-a.com/callback'; // 应用A的回调地址
res.redirect(`${redirectUri}?code=${code}`);
} else {
res.status(401).send('用户名或密码错误');
}
});
function generateAuthorizationCode() {
return Math.random().toString(36).substring(2, 15); // 随机字符串
}应用A或应用B使用授权码向认证中心兑换访问令牌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 app.post('/token', (req, res) => {
const { grant_type, code, client_id, client_secret, redirect_uri } = req.body;
// 验证授权码和客户端信息
if (grant_type === 'authorization_code' && isValidCode(code)) {
const accessToken = generateAccessToken(); // 生成随机访问令牌
res.json({ access_token: accessToken });
} else {
res.status(400).send('无效的授权码或客户端信息');
}
});
function generateAccessToken() {
return Math.random().toString(36).substring(2, 15); // 随机字符串
}
function isValidCode(code) {
// 验证授权码是否有效(这里仅作演示)
return true;
}
总结
- 用户访问应用A,前端检查本地是否有访问令牌。如果没有,则重定向到认证中心。
- 用户在认证中心登录成功后,认证中心生成授权码并重定向回应用A。
- 应用A使用授权码向认证中心兑换访问令牌,并保存到本地。
- 用户访问应用B,重复上述流程。由于用户已在认证中心登录,认证中心会直接返回授权码,无需再次输入用户名和密码。