go refresh-token需要存表校验吗?
这个问题的核心答案是:需要,而且强烈建议存表(持久化存储)。
但这里有一个关键误区:存表不是为了“校验”Token的真假(那是JWT签名做的),而是为了“状态管理”和“安全控制”。
下面我分三个层次帮你理清逻辑,并给出工业级的最佳实践方案:
1. 为什么必须存表?(存什么?)
如果只把 Refresh Token 当做普通的 JWT 来验签,那它和 Access Token 没有区别,失去了“可撤销”的意义。存表主要是为了管理以下状态:
黑名单(退出登录):用户主动退出,必须将对应的 Refresh Token 标记为失效,否则 Token 在有效期内依然可用。
绝对过期时间:JWT 本身带的
exp是相对时间。存表可以记录revoked_at(撤销时间)或expires_at,方便后台定时清理过期数据。设备/IP 绑定(安全风控):存储签发时的
device_id或ip,当刷新请求来自不同设备时,可以直接拒绝并强制重新登录。一次有效性(Reuse Detection):这是最关键的安全点。如果 Refresh Token 被黑客截获,他和你同时发起刷新请求。通过存表,第二次使用的那个 Token 会被标记为“已使用”,系统能立刻发现并撤销该用户家族的所有 Token。
2. 存表时,校验流程是怎样的?
不建议直接查数据库比对 Token 字符串(那太慢了),正确做法是双轨制:
验签(本地):先用密钥验证 JWT 的签名和有效期是否合法(防篡改)。
验状态(查表):验签通过后,拿着 Token 里的
jti(JWT ID,唯一标识)去查 Redis 或 DB。检查
status是否为revoked(已撤销)。检查
refresh_count是否超过阈值(防暴力刷新)。检查绑定的设备指纹是否匹配。
3. 存 MySQL 还是 Redis?(性能与安全的取舍)
这里有一个常见的性能优化策略,我强烈建议采用Redis 为主 + DB 为辅的架构:
| 存储方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 只存 MySQL | 数据绝对可靠,便于审计历史记录 | 高并发下,每次刷新都查 DB,IO 压力大 | 内部管理系统(并发低) |
| 只存 Redis | 性能极高,天然支持 TTL 自动过期 | 重启丢失数据(导致已撤销的 Token 再次生效) | 对可用性要求不高的场景 |
| Redis + 异步落库(推荐) | 兼顾高性能和高可用。Redis 做热数据校验,DB 做冷数据备份和审计 | 架构稍复杂 | 绝大多数生产环境 |
4. 标准表结构设计(极简版)
如果你用 MySQL,不需要存整个 Token 长字符串(那玩意太长了),利用 JWT 的jti作为主键即可:
sql
CREATE TABLE refresh_tokens ( jti CHAR(36) PRIMARY KEY, -- JWT的唯一ID,对应UUID user_id BIGINT NOT NULL, device_id VARCHAR(64), -- 设备指纹 refresh_count INT DEFAULT 0, -- 累计刷新次数(防滥用) revoked BOOLEAN DEFAULT FALSE, -- 是否撤销 expires_at DATETIME(3) NOT NULL, -- 绝对过期时间(创建索引便于清理) created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3), INDEX idx_user_device (user_id, device_id) -- 便于查询用户的所有活跃设备 );
5. 高级安全实践(必看)
如果你的系统对安全性要求较高(如金融、电商),建议执行Refresh Token 轮换(Rotation):
每次调用
/refresh接口时,不复用旧的 Refresh Token,而是生成一个新的 Refresh Token返回给客户端。在数据库中,软删除(标记 revoked)旧的
jti,并插入新的jti。核心逻辑:如果同一个
jti被请求了两次(说明新旧两个客户端在抢着刷新),立即将该用户所有设备的 Refresh Token 全部作废,强制全网重新登录。
总结建议
小型项目:存 Redis 就够了,利用
EXPIRE设置过期时间,校验时只查 Redis 是否存在且未标记为revoked。中大型项目:必须存表(DB)。因为 Refresh Token 的有效期通常很长(7天~30天),需要保证服务重启后依然能识别已撤销的 Token,并且需要审计日志。
一句话结论:存表不是为了验签(那是密码学的事),而是为了让你拥有“让Token提前失效”的绝对控制权。所以,一定要存。
如果你们项目并发量很高,想知道具体的 Redis Key 设计(比如是存 Hash 还是 String,以及如何原子性地处理轮换),我可以再展开说说,需要吗?😊
