共计 2463 个字符,预计需要花费 7 分钟才能阅读完成。
一、Token 重放攻击的危害:电商支付场景案例
去年我们系统遭遇了一次典型的 Token 重放攻击。攻击者通过 Burp Suite 截获用户支付请求后,在 10 分钟内重复发送了 37 次相同订单的支付 Token,导致用户被重复扣款。由于原始设计仅验证 Token 有效性而未检测重复使用,攻击者成功绕过系统验证。

这个案例暴露出三个关键问题:
- 身份认证 Token 缺乏时效性控制
- 服务端未记录已处理的请求标识
- 关键业务操作未做二次确认
二、三大防御方案原理对比
1. 时间戳窗口验证
- 原理:在 Token 中嵌入时间戳,服务端验证请求时间与服务器时间的偏差
- 优点:实现简单,无存储开销
- 缺点:依赖时钟同步,无法防御窗口期内的重放
2. Nonce 缓存机制
- 原理:每个请求携带唯一随机数 (Nonce),服务端缓存已使用的 Nonce
- 优点:完全防止重复请求
- 缺点:需要持久化存储,集群环境下需共享缓存
3. 签名验证机制
- 原理:用密钥对请求参数生成签名,服务端验证签名一致性
- 优点:可防篡改,无需存储状态
- 缺点:计算开销较大,密钥管理复杂
三、Spring Boot 整合防御方案实现
1. 基于 HMAC 的签名验证
/**
* 生成 HMAC-SHA256 签名
* @param secret 密钥
* @param params 请求参数 Map
* @return Base64 编码的签名
*/
public static String generateSignature(String secret, Map<String, String> params) {StringJoiner sj = new StringJoiner("&");
params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> sj.add(e.getKey() + "=" + e.getValue()));
Mac sha256 = Mac.getInstance("HmacSHA256");
sha256.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
return Base64.getEncoder().encodeToString(sha256.doFinal(sj.toString().getBytes())
);
}
2. Redis Nonce 校验器实现
@Repository
public class NonceRepository {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 检查并记录 Nonce
* @param nonce 随机字符串
* @param expireSeconds 过期时间 (秒)
* @return true 表示首次提交
*/
public boolean checkAndSet(String nonce, long expireSeconds) {Boolean result = redisTemplate.opsForValue()
.setIfAbsent("nonce:" + nonce, "1", expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
}
3. 时间漂移补偿逻辑
// 在拦截器中处理时间戳验证
if (Math.abs(System.currentTimeMillis() - requestTimestamp) > TIME_TOLERANCE) {
// 记录时间偏差值用于后续补偿
timeOffsetService.recordOffset(deviceId, requestTimestamp);
throw new InvalidTimestampException();}
// 时间补偿服务
public class TimeOffsetService {private final Map<String, Long> deviceOffsets = new ConcurrentHashMap<>();
public long getCompensatedTime(String deviceId) {return System.currentTimeMillis() +
deviceOffsets.getOrDefault(deviceId, 0L);
}
}
四、性能优化与安全增强
性能优化方案
- Redis 集群部署 :
- 采用 CRC16 分片算法
- 每个分片配置 1 主 2 从
-
使用 Redisson 客户端连接
-
签名计算优化 :
- 测试数据:单核 CPU 1000 次 HMAC-SHA256 计算耗时约 23ms
- 建议:对高频接口采用请求合并签名
安全增强措施
- 密钥轮换策略 :
- 每日自动生成新密钥
- 保留前两日密钥用于过渡期验证
-
使用 AWS KMS 或 HashiCorp Vault 管理
-
防时序攻击比较 :
// 使用 MessageDigest.isEqual 替代普通 equals
public static boolean safeEquals(String a, String b) {
return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8),
b.getBytes(StandardCharsets.UTF_8)
);
}
五、实战验证练习
1. 使用 Burp Suite 重现攻击
- 配置 Burp 拦截电商平台支付请求
- 修改金额参数后重放请求
- 观察服务端响应变化
2. Wireshark 抓包验证
- 过滤 HTTP 流量:
http.request.method == "POST" - 对比正常请求与重放请求的签名参数
- 验证 Nonce 重复时的 403 响应码
总结与建议
通过组合时间戳、Nonce 和签名验证,我们构建了立体防御体系。在实际项目中建议:
- 对关键业务接口启用三重验证
- 普通 API 至少使用时间戳 +Nonce
- 定期进行安全审计和渗透测试
- 监控异常重放请求并告警
这套方案在我们生产环境部署后,成功拦截了日均 300+ 次的重放攻击尝试,CPU 负载增加仅 2.1%,内存消耗增长可控。希望本文方案对您构建安全 API 有所帮助。
正文完
