Access Token 与 Refresh Token
只签发一个长期有效的 Access Token 看似简单,但被盗后攻击者也能长期使用。常见设计是把登录凭证拆成短期 Access Token 和长期 Refresh Token。
1. 职责对比
| 项目 | Access Token | Refresh Token |
|---|---|---|
| 用途 | 调用业务接口 | 换取新 Token |
| 发送频率 | 几乎每个受保护请求 | Access Token 过期或临近过期时 |
| 有效期 | 较短,如 15 分钟 | 较长,如 7 天 |
| 示例形态 | JWT | 高熵随机字符串或 JWT |
| 服务端状态 | 可以无状态验证 | 推荐关联服务端会话 |
| 泄露影响 | 短期冒用 | 可持续获得新 Access Token,风险更高 |
这些时间只是教学值,实际项目要根据风险、用户体验和撤销能力决定。
2. 为什么需要两种 Token
Access Token 短期有效
-> 泄露窗口较短
Refresh Token 长期有效且可撤销
-> 用户不必每 15 分钟重新输入密码
-> 服务端可通过会话记录终止登录
jsonwebtoken 只负责 JWT 的底层签发与验证,不会替你设计刷新、轮换、撤销和多设备会话。官方 README 也提醒自动刷新可能引入漏洞,因此刷新流程必须由业务系统谨慎设计。
3. 本教程的选择
本教程采用:
- Access Token:HS256 签名的 JWT。
- Refresh Token:
randomBytes(32)生成的随机字符串。 - 会话表:只保存 Refresh Token 的 SHA-256 摘要。
生成 Refresh Token:
import { randomBytes } from "node:crypto";
function createRefreshToken(): string {
return randomBytes(32).toString("base64url");
}
生成摘要:
import { createHash } from "node:crypto";
function hashRefreshToken(token: string): string {
return createHash("sha256").update(token).digest("hex");
}
为什么 Refresh Token 不使用 Argon2?
- 用户密码通常低熵、可猜,需要慢哈希抵抗暴力破解。
- Refresh Token 是 256 位安全随机值,几乎无法穷举。
- 服务端需要快速按摘要查找会话,SHA-256 更合适。
如果 Refresh Token 不是高熵随机值,这个结论就不成立。
4. 刷新流程
客户端提交 Refresh Token
-> 服务端计算 SHA-256
-> 查找会话
-> 检查会话未撤销、未过期
-> 生成新的 Access Token
-> 生成新的 Refresh Token
-> 更新会话中的摘要
-> 返回新凭证
5. Refresh Token Rotation
轮换意味着每次刷新后旧 Refresh Token 立即失效:
R1 --刷新--> A2 + R2
R1 再次使用 -> 拒绝
R2 --刷新--> A3 + R3
它能缩短旧 Token 被盗后的可用时间。如果系统要检测重放,可进一步保存 Token 家族关系:旧 Token 再次出现时,撤销整个家族并要求重新登录。
本教程实现基础轮换,但不展开完整 Token Family 数据结构。
6. 多设备登录
每次登录创建独立会话:
用户 123
|- 手机会话 session-a
|- 公司电脑 session-b
`- 家庭电脑 session-c
/logout:只撤销当前会话。/logout-all:撤销该用户所有会话。
因此不应只在用户表保存一个 Refresh Token,否则新设备登录可能覆盖旧设备,也难以分别退出。
7. 浏览器如何保存 Token
没有适用于所有项目的唯一答案。常见折中是:
- Access Token:保存在前端内存中,随
Authorization请求头发送。 - Refresh Token:放入
HttpOnly、Secure、合适SameSite的 Cookie。
需要同时理解:
localStorage可被成功执行的 XSS 脚本读取。HttpOnlyCookie 不能被前端脚本直接读取,但浏览器会自动携带,需要防范 CSRF。Secure要求 HTTPS。SameSite需要根据是否跨站部署谨慎配置。
教程为了突出后端流程,示例通过 JSON 请求体传 Refresh Token。生产浏览器应用通常应改成安全 Cookie,并配套 CSRF 和 CORS 策略。
8. Access Token 过期后的状态码
业务接口收到过期 Access Token:
HTTP/1.1 401 Unauthorized
客户端可以尝试调用刷新接口。如果 Refresh Token 也无效或会话已撤销,则要求用户重新登录。
9. 退出登录的真实含义
只删除客户端 Token 不足以完成可靠退出:被复制的 Refresh Token 仍可能存在。
完整退出应包括:
- 服务端撤销对应会话。
- 客户端删除 Access Token。
- 客户端删除 Refresh Token 或清除 Cookie。
已签发的 Access Token 通常会继续有效到 exp,这也是其有效期应较短的原因。