spring mvc 拦截器实现基于 token 的防重放攻击,核心是阻断已过期或已被使用过的请求,要求 token 具备时效性、一次性及客户端绑定属性,通过时间戳+hmac签名+redis缓存校验实现。

Spring MVC 中用拦截器实现基于 Token 的接口防重放攻击,核心不是单纯“防止重复提交”,而是要阻断**已过期或已被使用过的请求再次被服务器接受**——即防御重放(replay)攻击。这要求 Token 具备时效性、一次性、绑定客户端特征(如 IP/UA)等关键属性,不能只靠 session 存一个随机字符串就完事。
Token 必须带时间戳和签名
防重放的关键是让每个请求的 Token 具备不可复用性:
- 服务端生成 Token 时,必须包含当前毫秒级时间戳(如 System.currentTimeMillis()),并用密钥对时间戳 + 用户标识(如 userId 或 sessionId)做 HMAC-SHA256 签名
- 例如:token = base64encode(timestamp + ":" + hmac(timestamp + userId, secret))
- 前端在请求头(如 X-Request-Token)或参数中携带该 Token,服务端解析后验证:时间戳是否在允许窗口内(如 ±5 分钟)、签名是否匹配
- 不依赖 session 存储,避免分布式环境下 session 不一致问题;推荐用 Redis 缓存已使用的 token(以签名值为 key,过期时间设为窗口上限,如 300 秒)
拦截器中校验逻辑要严格
preHandle 阶段完成全部校验,任一失败即拒绝请求:
- 提取请求中的 Token 字符串,尝试 base64 解码并分割出 timestamp 和 signature
- 检查 timestamp 是否超出容忍范围(如 Math.abs(System.currentTimeMillis() - timestamp) > 300_000)
- 重新计算 signature:hmac(timestamp + userId, secret),与客户端传入的 signature 比对(用 MessageDigest.isEqual 防时序攻击)
- 查 Redis 判断该 signature 是否已存在(表示已被消费),存在则拒绝;校验通过后立即写入 Redis 并设 TTL
- 若用户未登录或 userId 获取失败,直接拦截——重放防护必须建立在身份可识别基础上
前端需配合生成合法 Token
Token 不应由前端自行生成,而应由服务端在登录成功或获取访问凭证时下发:
- 登录接口返回 { "token": "xxx", "expiresIn": 300 },前端缓存并在后续请求中携带
- 前端 SDK 在发起请求前自动注入 Token 到 header,并确保每次请求 timestamp 是实时生成的(服务端只认这个时间点)
- 禁止前端缓存旧 Token 多次复用;建议在 Axios 拦截器中统一处理 Token 刷新与注入逻辑
- 敏感操作(如支付、修改密码)可额外要求二次校验(如短信验证码),不单靠 Token
与 CSRF 防护区分清楚
防重放 ≠ 防 CSRF,二者目标不同,实现也不同:
- CSRF 防护关注“请求是否来自合法页面”,靠同步令牌(Synchronizer Token Pattern)+ Referer/Origin 校验
- 重放防护关注“请求是否被截获后原样重发”,靠时间戳 + 签名 + 一次性消费机制
- 两者可共存:CSRF Token 放表单 hidden 字段,重放 Token 放请求头;拦截器里分别校验,互不干扰
- 注意:若用 Cookie 自动携带认证凭证(如 JSESSIONID),必须启用 SameSite=Lax/Strict,并配合 HttpOnly,否则重放风险更高
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











