单token方案因access_token过期时间短致频繁登录、拉长有效期又增被盗风险,双token机制通过分离短期访问权限(access_token)与长期续期能力(refresh_token),兼顾安全与体验。

为什么不能只用单个 access_token?
单 token 方案在实际项目中很快会遇到两个硬伤:access_token 过期时间短,频繁重登录影响体验;过期时间拉长又增大被盗用风险。双 Token(access_token + refresh_token)是标准解法:前者短时有效、高频使用;后者长期存储、仅用于换新 access_token,且必须绑定设备/IP/UA 等上下文。
关键不是“有没有 refresh_token”,而是它是否被正确校验和作废。常见错误包括:refresh_token 未设过期时间、未与用户 session 绑定、换 token 后旧 refresh_token 仍可用(缺乏黑名单或版本号机制)。
如何安全生成和存储 refresh_token?
refresh_token 必须满足:密码学安全随机、不可预测、一次性或有限次使用(推荐“单次有效 + 换发新 token”)。不要用用户 ID 或时间戳拼接,直接用 crypto/rand.Read 生成 32 字节以上随机值,再 hex 编码:
b := make([]byte, 32) _, _ = rand.Read(b) rt := hex.EncodeToString(b)
存储时注意:
-
refresh_token的哈希值(如sha256(rt))存数据库,明文绝不落地 - 关联字段必须包含:
user_id、issued_at、expires_at、fingerprint(可选:IP + User-Agent 哈希) - 设置合理过期时间(如 7–30 天),并启用“滑动过期”逻辑(每次成功刷新后更新
expires_at)
refresh 接口必须做哪些校验?
/auth/refresh 接口不是“给个 token 就发新 access_token”。它必须原子化完成四件事:
- 校验
refresh_token哈希是否存在且未过期 - 核对请求携带的
fingerprint与数据库记录一致(防 token 泄露后跨设备滥用) - 检查对应
user_id是否被禁用或密码已变更(需关联用户状态表) - 作废当前
refresh_token(删库 or 标记revoked=true),并生成并存储新refresh_token
漏掉任意一步都可能造成越权或 token 泄露放大。特别注意:数据库操作必须在单事务中完成,避免并发刷新导致旧 token 重复使用。
access_token 过期后怎么无感续期?
前端无法真正“无感”,但可以最小化交互:在 access_token 剩余有效期 /auth/refresh 换新。后端返回新 access_token 和新 refresh_token(带新过期时间),前端覆盖本地存储。
容易踩的坑:
- 前端未校验响应中的
refresh_token是否变化,导致后续刷新始终用旧值 - 未处理并发请求:多个 API 同时 401,触发多次 refresh 调用,引发“refresh_token 已被作废”错误
- 客户端未统一拦截 401 并挂起后续请求,导致部分请求直接失败而非等待刷新完成
最稳妥做法是封装一个 request 中间件,内部维护 refresh 状态锁(如 sync.Once + channel),确保同一时刻只发起一次刷新。
双 Token 的复杂度不在生成,而在生命周期管理——refresh_token 的作废时机、绑定粒度、存储安全,每处松动都会让整个认证体系失效。











