JWT单点登录学习入门介绍了一种基于JWT实现跨应用安全身份验证的方法,适用于无状态的服务。文章详细讲解了JWT的基础概念、单点登录的重要性以及如何使用JWT实现单点登录的步骤。通过示例代码,读者可以轻松上手实践JWT单点登录的开发。JWT单点登录不仅提升了用户体验,还有效降低了系统的维护成本。
JWT单点登录学习入门指南 JWT基础概念介绍什么是JWT
JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式用于通信双方之间安全地传输信息。JWT可以被用于身份验证和信息交换。它由三部分组成:头部(Header)、载荷(Payload)、签名(Signature)。
JWT的构成
JWT由三部分组成,每部分由一个点(.
)分隔,分别是头部、载荷和签名:
-
头部:包含令牌的类型(即JWT)和所使用的签名算法信息(例如HMAC SHA256或RSA)。示例头部:
{ "typ": "JWT", "alg": "HS256" }
-
载荷:载荷可以存放声明(声明是键值对形式的简单数据),这些声明是JWT的主体部分,通常用于存放用户的标识、角色、权限等信息。示例载荷:
{ "sub": "1234567890", "name": "John Doe", "admin": true, "exp": 1516239022 }
- 签名:签名用于保证JWT的完整性和防篡改。它通过头部定义的算法,使用头部的密钥和载荷数据计算生成。
JWT的优势
- 无状态:服务器无需保存JWT的状态信息,减轻了服务器的负担。
- 安全性:生成的JWT是不可篡改的,客户端可以在每次请求时携带JWT,服务器可以验证其有效性。
- 可扩展性:可以在载荷中添加更多的自定义信息,而不会影响到原来的JWT结构。
- 易于传输:JWT是一个简单的字符串,可以通过HTTP请求头或URL参数进行传输。
JWT的劣势
- 大小限制:JWT的大小有限制,超过一定长度可能会影响性能。
- 安全性依赖于密钥:JWT的安全性完全依赖于密钥的安全性,一旦密钥泄露,所有使用该密钥生成的JWT都会失效。
单点登录定义
单点登录(Single Sign-On,简称SSO)是一种身份验证机制,允许用户使用一组凭证登录一个系统后,无需再次输入凭证即可访问其他系统。这种机制能够提升用户体验,减少凭证管理的复杂度。
单点登录的重要性
- 用户体验:用户无需记住多个账户密码,简化了登录过程。
- 安全性:通过集中管理用户凭证,减少安全漏洞的风险。
- 维护成本:集中管理用户身份,减少了维护多个系统的开销。
常见单点登录实现方式
- Cookie:通过共享Cookie实现,但这种方式依赖于浏览器Cookie,不适用于无状态服务。
- OAuth:OAuth是一种开放授权协议,可以实现跨应用的单点登录,需要中间件支持。
- JWT:通过JWT实现单点登录,适用于无状态服务,易于跨域使用。
准备工作
- 环境准备:确保已安装Node.js和npm。
- 工具选择:选择适合的开发工具,例如VSCode。
- 库安装:使用npm安装JWT相关库。
npm install jsonwebtoken express body-parser cors
创建JWT
创建JWT需要生成头部、载荷和签名。头部和载荷是JSON对象,签名是通过指定的算法生成的。
const jwt = require('jsonwebtoken');
// 创建头部
const header = {
alg: 'HS256',
typ: 'JWT'
};
// 创建载荷
const payload = {
user: 'john_doe',
role: 'admin',
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 有效时间为1小时
};
// 生成签名
const secret = 'supersecretkey';
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });
console.log(token);
验证JWT
验证JWT需要检查签名和载荷的有效性。
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiam9obiRkb2UiLCJyb2xlIjoiYWRtaW4iLCJleHAiOiIyMDE5LTA3LTIwVDA5OjEwOjQ4LjI1OVoiLCJzdWIiOiIxMjM0NTY3ODkwIn0.0XwP4E0ZiR8w4lJW4BQW301ZcF482U492w0H42w94';
try {
const decoded = jwt.verify(token, secret);
console.log(decoded);
} catch (err) {
console.error(err);
}
实现用户身份验证
const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
const app = express();
const secret = 'supersecretkey';
const port = 3000;
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 这里应该有用户验证逻辑
const user = { username: "john_doe", password: "password123" };
if (username === user.username && password === user.password) {
const token = jwt.sign({ username: user.username }, secret, { expiresIn: '1h' });
res.json({ token });
} else {
res.status(401).json({ message: 'Invalid credentials' });
}
});
// 需要身份验证的接口
app.get('/protected', (req, res) => {
const token = req.headers.authorization;
if (token) {
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(403).json({ message: 'Invalid token' });
}
res.json({ message: 'Welcome authenticated user', user: decoded });
});
} else {
res.status(403).json({ message: 'No token provided' });
}
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
跨域请求中的JWT使用
const cors = require('cors');
app.use(cors({
credentials: true,
origin: ['http://localhost:3001'] // 允许跨域请求的前端地址
}));
// 在前端获取token后添加到请求头部
fetch('http://localhost:3000/protected', {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` },
credentials: 'include'
}).then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
实际案例演示
选择编程语言和框架
选择Node.js作为服务端语言,使用Express框架。Node.js和Express是开发无状态服务的理想选择,因为它们轻量、易于扩展,且能够很好地支持异步操作。
搭建开发环境
- 创建新的Node.js项目
mkdir jwt-sso cd jwt-sso npm init -y
- 安装必要的依赖
npm install express jsonwebtoken body-parser cors
编写代码实现JWT单点登录
- 创建登录接口
const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const secret = 'supersecretkey';
const port = 3000;
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cors({ credentials: true, origin: ['http://localhost:3001'] }));
const users = [
{ username: 'john', password: '12345' },
{ username: 'jane', password: '56789' }
];
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(user => user.username === username && user.password === password);
if (user) {
const token = jwt.sign({ username: user.username }, secret, { expiresIn: '1h' });
res.json({ token });
} else {
res.status(401).json({ message: 'Invalid credentials' });
}
});
// 需要身份验证的接口
app.get('/protected', (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (token) {
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(403).json({ message: 'Invalid token' });
}
res.json({ message: 'Welcome authenticated user', user: decoded });
});
} else {
res.status(403).json({ message: 'No token provided' });
}
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
- 前端代码
<!DOCTYPE html>
<html>
<head>
<title>JWT SSO Demo</title>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<h1>Login</h1>
<form id="loginForm">
<label for="username">Username:</label>
<input type="text" id="username" name="username"><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password"><br>
<button type="submit">Login</button>
</form>
<script>
const loginForm = document.getElementById('loginForm');
loginForm.addEventListener('submit', (e) => {
e.preventDefault();
const username = loginForm.elements['username'].value;
const password = loginForm.elements['password'].value;
axios.post('http://localhost:3000/login', { username, password })
.then(response => {
localStorage.setItem('token', response.data.token);
alert('Login successful');
})
.catch(error => {
console.error(error);
alert('Login failed');
});
});
const protectedButton = document.getElementById('protectedButton');
protectedButton.addEventListener('click', () => {
const token = localStorage.getItem('token');
axios.get('http://localhost:3000/protected', {
headers: { 'Authorization': `Bearer ${token}` }
})
.then(response => {
console.log(response.data);
alert('Protected resource accessed successfully');
})
.catch(error => {
console.error(error);
alert('Failed to access protected resource');
});
});
</script>
<button id="protectedButton">Access Protected Resource</button>
</body>
</html>
常见问题及解决方法
JWT时间戳问题解决
- 过期时间设置:在生成JWT时设置过期时间(例如1小时)。
- 自动刷新:前端可以存储过期时间,当请求时检查时间戳是否过期,如果过期则重新登录或刷新JWT。
JWT安全性提升策略
- 密钥安全:密钥应妥善保管,不应泄露。
- 签名算法:使用更安全的签名算法,如RS256。
- 传输安全:确保JWT通过HTTPS传输,防止中间人攻击。
身份验证错误处理
- 错误提示:当用户登录失败时显示具体的错误信息。
- 前端处理:前端在接收到错误响应时提示用户,而不是直接显示错误信息。
- 日志记录:服务端记录详细的错误日志,便于排查问题。
JWT单点登录总结
JWT单点登录是实现跨应用身份验证的一种有效手段。通过JWT,开发者可以轻松地在无状态的服务中实现安全的身份验证。同时,JWT的无状态性、安全性以及易于传输的特点使其成为现代应用程序的首选。
推荐进一步学习的资源
- 慕课网:提供了丰富的在线课程,涵盖从基础到高级的JWT单点登录教程。
- 官方文档:JWT官方文档提供了详细的用法和最佳实践。
- 在线教程:Web开发社区和论坛,如Stack Overflow,提供了大量的实际案例和问题解答。
共同學習,寫下你的評論
評論加載中...
作者其他優質文章