要实现刷新 Token(Refresh Token)机制,通常的做法是为用户提供两个 Token:Access Token 和 Refresh Token。以下是实现步骤和关键点:
1. Access Token 和 Refresh Token 的区别
- Access Token: 短生命周期的 Token,用于访问受保护资源。
- Refresh Token: 长生命周期的 Token,用于获取新的 Access Token,而无需重新登录。
2. 修改 .env
文件
在 .env
中添加 Refresh Token 的密钥和过期时间:
PORT=3000
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRATION=1h
REFRESH_TOKEN_SECRET=your_refresh_token_secret_key
REFRESH_TOKEN_EXPIRATION=7d
3. 登录时生成 Access Token 和 Refresh Token
在 auth.js
的 /login
路由中,修改代码以同时生成 Access Token 和 Refresh Token:
// 登录路由
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
// 校验输入
if (!username || !password) {
return res.status(400).json({ message: 'Username and password are required' });
}
// 查找用户
const user = users.find(user => user.username === username);
if (!user) {
return res.status(400).json({ message: 'Invalid credentials' });
}
// 验证密码
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ message: 'Invalid credentials' });
}
// 生成 Access Token
const accessToken = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRATION });
// 生成 Refresh Token
const refreshToken = jwt.sign({ userId: user.id }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: process.env.REFRESH_TOKEN_EXPIRATION });
// 将 Refresh Token 存储到用户对象中(或数据库中)
user.refreshToken = refreshToken;
saveUsers(); // 如果使用内存存储,则需要手动保存
res.status(200).json({ message: 'Login successful', accessToken, refreshToken });
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
}
});
4. 实现 Refresh Token 路由
创建一个 /refresh-token
路由,允许用户通过有效的 Refresh Token 获取新的 Access Token。
// Refresh Token 路由
router.post('/refresh-token', (req, res) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({ message: 'Refresh token is required' });
}
// 验证 Refresh Token
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, decoded) => {
if (err) {
return res.status(403).json({ message: 'Invalid refresh token' });
}
// 查找用户并验证 Refresh Token 是否匹配
const user = users.find(user => user.id === decoded.userId && user.refreshToken === refreshToken);
if (!user) {
return res.status(403).json({ message: 'Refresh token does not match' });
}
// 生成新的 Access Token
const newAccessToken = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRATION });
res.status(200).json({ accessToken: newAccessToken });
});
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
}
});
5. 存储 Refresh Token
为了安全起见,建议将 Refresh Token 存储在服务器端(如数据库)。如果继续使用内存存储,可以修改 users
数组来保存每个用户的 Refresh Token。
例如,在注册或登录时更新用户对象:
const newUser = { id: Date.now(), username, password: hashedPassword, refreshToken: '' };
users.push(newUser);
6. 注销功能(可选)
为了提高安全性,可以提供注销功能,使 Refresh Token 失效。
// 注销路由
router.post('/logout', authenticateToken, (req, res) => {
try {
const { userId } = req.user;
// 找到用户并清除 Refresh Token
const user = users.find(user => user.id === userId);
if (user) {
user.refreshToken = '';
saveUsers(); // 如果使用内存存储,则需要手动保存
}
res.status(200).json({ message: 'Logout successful' });
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
}
});
7. 安全性注意事项
- Refresh Token 的存储: 始终将 Refresh Token 存储在服务器端(如数据库),而不是客户端 Cookie 或 LocalStorage。
- Token 黑名单: 可以维护一个黑名单,记录已失效的 Refresh Token。
- 定期轮换 Refresh Token: 每次使用 Refresh Token 时,生成一个新的 Refresh Token 并替换旧的。
- HTTPS: 确保应用运行在 HTTPS 上,防止 Token 在传输过程中被窃取。
8. 示例测试
登录:
发送 POST 请求到 /api/auth/login
,携带用户名和密码,返回 Access Token 和 Refresh Token。
刷新 Token:
发送 POST 请求到 /api/auth/refresh-token
,携带 Refresh Token,返回新的 Access Token。
注销:
发送 POST 请求到 /api/auth/logout
,使 Refresh Token 失效。
通过以上步骤,实现了一个带有 Refresh Token 的 JWT 认证系统!
注:本章涉及到的Express 、dotenv、jsonwebtoken框架
评论区