引言💭
在之前的文章Web身份认证与状态管理:Cookie、Session 与 JWT中提到了JWT,这篇文章再来介绍一下它的进阶版本,双Token机制。
一、什么是双 Token 机制?🤔
双 Token 机制是一种通过使用两种不同类型的令牌来管理用户身份认证和授权的方案。通常,这两种令牌是:
- Access Token(访问令牌):用于身份验证和授权,通常有效期较短,用于每次请求时的验证。
- Refresh Token(刷新令牌):用于获取新的 Access Token,通常有效期较长,帮助用户在 Access Token 过期后无缝继续访问应用。
二、JWT 与双 Token 机制的关系👥
JWT(JSON Web Token)是一种常用的令牌格式,广泛用于双 Token 机制中的 Access Token 实现。JWT 采用签名技术,确保数据的完整性和安全性。其结构由三部分组成:
- Header(头部) :指定令牌的类型(JWT)和签名算法。
- Payload(负载) :包含声明信息,如用户身份标识(sub)、过期时间(exp)等。
- Signature(签名) :使用密钥对前两部分进行签名,用来验证数据完整性和防止篡改。
因此,在双 Token 机制中,Access Token 通常采用 JWT 格式,而 Refresh Token 则是另一个长期有效的令牌。两者结合使用,能够提供高效且安全的认证方案。
三、双 Token 的工作流程📌
双 Token 机制的工作流程如下:
- 用户登录 :用户通过用户名和密码登录系统,服务器验证用户身份后,生成并返回 Access Token 和 Refresh Token。
- 访问受保护资源 :客户端使用 Access Token 向服务器请求资源,服务器验证 Access Token 的有效性。
- Access Token 过期 :当 Access Token 过期时,客户端通过 Refresh Token 请求新的 Access Token,无需重新登录。
- 获取新 Access Token :服务器验证 Refresh Token 的有效性后,返回新的 Access Token,客户端继续访问受保护资源。
- Refresh Token 过期 :如果 Refresh Token 过期,用户需要重新登录,重新获取新的 Access Token 和 Refresh Token。

四、双 Token 机制的优势✨
1. 提高安全性
将 Access Token 存储在内存中可以减少暴露风险,因为内存中的数据在页面刷新时会清空。由于 Access Token 有较短的有效期,这样的存储方式非常适合短期使用。
将 Refresh Token 存储在带有 HttpOnly 属性的 cookie 中,则有效阻止了 JavaScript 访问该 token,增加了安全性。这种方法通常用于防止 XSS 攻击,使得即使攻击者能够执行 JavaScript 代码,也无法窃取 token。
这种方式结合了安全性和便捷性,确保了 Access Token 的短期使用和 Refresh Token 的长期存储在安全的位置,从而平衡了用户体验与安全性。
2. 无缝用户体验
使用 Refresh Token 可以避免用户在 Access Token 过期时重新登录。只要 Refresh Token 仍然有效,客户端就能自动获取新的 Access Token,提供无缝的用户体验,尤其适合需要长时间在线的应用,如社交媒体或电商平台。
3. 无状态认证
JWT 是无状态的,意味着服务器不需要存储会话信息。所有身份和权限信息都封装在 JWT 中,通过验证签名和有效期来确保请求的合法性。双 Token 机制进一步增强了这一点,服务器只需存储 Refresh Token,而无需管理用户的会话状态。
五、如何实现双 Token 机制?🤔
1. 生成 JWT Access Token 和 Refresh Token
使用 JWT 库生成 Access Token 和 Refresh Token。
- Access Token 通常包含用户的身份信息和过期时间,签名用于验证其完整性。
- Refresh Token 长期有效,用于刷新 Access Token。
代码示例:
perl
const jwt = require('jsonwebtoken');
// 生成 Access Token
function generateAccessToken(user) {
return jwt.sign(
{ sub: user.id },
'secret',
{ expiresIn: '15m' } // 短时效
);
}
// 生成 Refresh Token
function generateRefreshToken(user) {
return jwt.sign(
{ sub: user.id },
'refreshSecret',
{ expiresIn: '7d' } // 长时效
);
}
2. 存储 Refresh Token
Refresh Token 可以存储在服务器端的数据库中,或通过安全的方式(如 HTTPOnly cookies)存储在客户端。它的有效期通常较长。
3. 验证 Access Token
每次客户端发起请求时,Access Token 会包含在 HTTP 头部的 Authorization
字段中,服务器通过验证其有效性来判断请求是否合法。
scss
// 验证 Access Token
function verifyAccessToken(req, res, next) {
// 获取 Bearer Token 的第二部分(实际 Token 值)
const token = req.headers['authorization']?.split(' ')[1];
if (!token) return res.status(403).send('Access denied.'); // 验证token是否存在
// 验证token
jwt.verify(token, 'secret', (err, user) => {
if (err) return res.status(403).send('Invalid or expired token.');
req.user = user; // 将解码后的用户信息(payload)附加到 `req.user` 上
next();
});
}
4. 使用 Refresh Token 刷新 Access Token
当 Access Token 过期时,客户端将 Refresh Token 发送到服务器,服务器验证 Refresh Token 后生成新的 Access Token。
scss
// 使用 Refresh Token 刷新 Access Token
function refreshAccessToken(req, res) {
// 从请求体(body)中获取客户端提交的 refreshToken
const refreshToken = req.body.refreshToken;
// 检查 Refresh Token 是否存在
if (!refreshToken) return res.status(403).send('Refresh Token required.');
// 验证 Refresh Token
jwt.verify(refreshToken, 'refreshSecret', (err, user) => {
if (err) return res.status(403).send('Invalid refresh token.');
// 调用 generateAccessToken 函数生成新的 Access Token 并返回给客户端
const newAccessToken = generateAccessToken(user);
res.json({ accessToken: newAccessToken });
});
}
结语✒️
双 Token 机制结合了 Access Token 和 Refresh Token 的优势,既保证了安全性,又提升了用户体验。它使得 Web 应用能够在保证无状态认证的前提下,有效管理会话和身份验证的生命周期。