共计 2091 个字符,预计需要花费 6 分钟才能阅读完成。
核心概念:JWT 与双 Token 机制
- JWT 结构剖析
JWT 由 Header.Payload.Signature 三部分组成: - Header 包含算法类型(如 HS256)和 token 类型
- Payload 存储用户 ID 等非敏感声明(注意不要放密码)
-
Signature 防止篡改,用密钥对前两部分签名

-
Access Token 设计
通常设置 15-30 分钟过期(如银行应用更短),特点: - 高频使用:每个 API 请求都携带
- 风险较高:一旦泄露相当于短期密码
-
无状态验证:服务端只需验证签名不需查库
-
Refresh Token 设计
通常设置 7 -30 天过期(甚至更长),特点: - 低频使用:仅用于获取新 Access Token
- 严格保管:服务端需要存储验证
- 可撤销性:发现异常立即废止
为什么需要双 Token?解决三大痛点
-
无状态扩展需求
传统 Session 需要服务器存储状态,分布式环境下扩展困难 -
Token 劫持防护
Access Token 短期有效,即使泄露攻击窗口也很小 -
用户体验平衡
避免频繁登录(Refresh Token 保持会话),又保证安全性(Access Token 短周期)
Node.js 实战:完整认证流实现
// 密钥管理(实际项目应从环境变量读取)const ACCESS_SECRET = 'access_secret_123!@#';
const REFRESH_SECRET = 'refresh_secret_456$%^';
// 签发双 Token
function generateTokens(user) {
const accessToken = jwt.sign({ userId: user.id},
ACCESS_SECRET,
{expiresIn: '15m'} // 关键:设置短过期时间
);
const refreshToken = jwt.sign({ userId: user.id, tokenVersion: user.tokenVersion},
REFRESH_SECRET,
{expiresIn: '7d'} // 关键:设置长过期时间
);
return {accessToken, refreshToken};
}
// 刷新 Access Token 端点
app.post('/refresh-token', async (req, res) => {
try {const { refreshToken} = req.body;
// 验证 Refresh Token 有效性和版本
const payload = jwt.verify(refreshToken, REFRESH_SECRET);
const user = await User.findById(payload.userId);
if (user.tokenVersion !== payload.tokenVersion) {return res.status(401).send('Token revoked'); // 关键:版本校验
}
// 颁发新 Access Token
const {accessToken} = generateTokens(user);
res.json({accessToken});
} catch (err) {res.status(403).send('Invalid token');
}
});
安全最佳实践
- 传输层防护
- 强制 HTTPS 防止中间人攻击
-
不要将 Token 放在 URL 参数中
-
存储方案对比
| 方案 | 防 XSS | 防 CSRF | 适用场景 |
|—————–|——-|——–|——————-|
| HttpOnly Cookie | ✓ | ✗ | 最安全推荐方案 |
| LocalStorage | ✗ | ✓ | 需要额外 CSRF 防护 | -
撤销机制
- 在用户表中增加
tokenVersion字段 - 修改密码 / 注销时递增版本号
- 使旧 Refresh Token 立即失效
性能与分布式注意事项
-
验证优化
JWT 验证虽不查库,但签名计算有开销,网关层可缓存公钥 -
分布式一致性
两种处理方式: - 方案 A:所有服务共享 Refresh Token 存储(如 Redis 集群)
- 方案 B:每个服务独立校验,通过事件同步撤销状态
新手避坑指南
- 过期时间设置
- Access Token 不超过 1 小时
- Refresh Token 建议 7 -30 天
-
重要系统可缩短至分钟级
-
并发刷新处理
可能出现场景: - 多标签同时发起刷新请求
-
解决方案:
// 使用 Redis 原子操作 const key = `refresh_lock:${userId}`; const isLocked = await redis.set(key, '1', 'NX', 'EX', 5); if (!isLocked) throw new Error('操作过于频繁'); -
日志与监控
- 记录异常 Token 使用
- 报警频繁刷新行为
思考进阶方向
- 如何实现跨域 SSO(单点登录)的 Token 共享方案?
- 对于 IoT 设备这类无法安全存储 Refresh Token 的场景,替代方案是什么?
- 如何设计阶梯式过期策略(如连续活跃用户延长会话)?
通过这套双 Token 机制,我们既获得了无状态架构的优势,又通过分层安全设计有效控制了风险。建议首次实现后使用 Postman 等工具模拟各种异常流(如过期 Token、篡改 Token 等),确保防御措施生效。
正文完

