JWT 默认签名机制无法防止重放,必须结合时间戳、随机数或 Redis 黑名单;HS256 仅保证内容完整性,不防重复使用;需用 jti 唯一标识 + Redis 存储实现一次性校验,并配合前端 nonce 和安全存储策略。

单靠 JWT 默认签名机制无法防止重放,必须叠加时间戳、随机数或 Redis 黑名单才能生效。
为什么 HS256 签名本身不防重放
JWT 的 Signature 只保证「内容未被篡改」,不保证「请求只被使用一次」。攻击者截获一个有效 Token(比如带 exp 的),只要在过期前反复发送,服务器默认会一直接受——因为签名验证通过、时间未超限。
- 常见错误:只校验
exp和iat,就认为安全 - 真实风险:中间人抓包后重放登录后的任意请求(如转账、删数据)
- 关键点:
exp是有效期,不是“一次性”开关
用 jti + Redis 实现一次性 Token 校验
JWT 标准支持 jti(JWT ID)字段,它应是服务端生成的唯一字符串。每次签发 Token 时写入 Redis,并设置与 Token 相同的过期时间;验证时先查 jti 是否已存在,存在则拒绝。
- 示例代码片段中,
GenerateToken必须生成 UUID 填入jti,不能用用户 ID 或时间戳代替 - Redis key 推荐格式:
jwt:jti:{jti_value},value 可设为1或空字符串 - 注意:Redis 连接失败时需降级处理(如跳过校验并记录告警),不能直接 panic 或阻塞请求
- 如果用集群 Redis,确保
SET操作带EX和NX参数,避免并发重复写入
前端必须配合的两个动作
后端加了 jti 校验,前端不配合也白搭。重点不是“怎么发”,而是“怎么不重复发”:
- 所有敏感操作(POST/PUT/DELETE)必须携带新生成的
nonce(随机字符串),由前端每次请求前生成并附在请求体或 header 中(如X-Nonce) - 登录成功后立即刷新 Token,旧 Token 不再复用——别让前端缓存多个有效 Token
- 不要把 Token 存在 localStorage;改用 httpOnly + secure Cookie,降低 XSS 泄露风险
- 若用 Axios,可在拦截器里统一注入
nonce,并禁止手动拼接请求
别忽略时钟漂移和网络延迟
即使加了 nbf(not before)和 exp,客户端和服务端系统时间不一致会导致误判。尤其在移动端或 Docker 容器中,NTP 同步可能滞后。
- 服务端校验时,建议允许最多 5 秒的时钟偏差(
WithValidTime或自定义验证函数) - 不要依赖客户端传来的
iat做逻辑判断,一律以服务端time.Now()为准 - Redis 过期时间必须严格等于
exp - iat,不能硬编码为固定值(比如 2 小时),否则 Token 提前失效或延迟失效
重放防护本质是「状态 + 时间 + 唯一性」三者缺一不可。只做一半,等于没做。


















