Skip to content

网关层如何防止接口重放攻击?如果攻击者抓取了请求包进行重放,你如何识别和拦截? ​

  • 标签:后端、系统设计、场景题、API网关、安全

🏛 业务架构师 ​

防重放攻击的核心方案是**"时间窗 + 一次性随机数 + 签名"三件套**,我先讲机制,再讲识别与拦截的层次,最后点出幂等是最后防线。

签名与防重放协议(服务端与调用方约定)

请求参数 + timestamp + nonce + app_secret
→ 按参数名排序拼接 → HMAC-SHA256 → sign

服务端校验三步:

  1. 时间窗校验:|now - timestamp| < 5 分钟——重放窗口外的请求直接拒绝(时间窗不能太长,太长给攻击者留空间;太短要求双方时钟同步,NTP 保证)
  2. nonce 一次性:SETNX nonce:{nonce} EX 300——窗口内的重放,同一 nonce 只接受第一次,Redis 记录已用的 nonce(TTL = 时间窗长度即可,过期后时间窗校验自然拦截)
  3. 签名比对:防止攻击者伪造 timestamp/nonce——三者绑定在签名里,篡改任何一项签名校验失败

识别与拦截(纵深防御)

  • 网关层:时间戳/nonce/sign 参数缺失直接拒绝;nonce 命中已用记录 → 标记攻击源(IP/设备进观察名单)
  • 行为识别:同一 appid 高频重复 nonce 尝试 → 限流/封禁
  • 业务幂等键:关键操作(支付、领券)带唯一请求号,服务端唯一索引兜底——就算前两层全被绕过(比如密钥泄漏),幂等保证攻击最多造成一次真实效果

密钥管理(安全的地基)

  • secret 分发走安全通道、定期轮换(轮换期新旧双验平滑过渡)
  • 签名算法版本化(sign-version),算法升级不停服

业务收尾:重放攻击的真实威胁是资金类接口——重复支付请求、重复领券。我的原则:签名防"外人",幂等防"一切"(包括合法用户的网络重试),两层都要有,幂等永远不能省。

🏗 系统架构师 ​

防重放的核心是让每个请求"一次性、有时效、有身份",我按机制、实现、边界攻击讲。

1. 基础三要素:timestamp + nonce + 签名 客户端请求携带:

text
timestamp=1698000000          请求时间
nonce=8f3a...                 随机串(请求唯一标识)
sign=HMAC(app_secret, method+path+timestamp+nonce+body)

服务端校验顺序:

  1. 签名验证:用相同算法重算比对,防篡改(任何参数被改,签名对不上)
  2. 时效验证:now - timestamp < 5分钟,超时拒绝——重放窗口被限定
  3. nonce 查重:nonce 存 Redis SETNX,TTL = 时效窗口;已存在即为重放,拒绝

三要素缺一不可:没有 nonce,5 分钟内可原样重放;没有 timestamp,nonce 要永久存储;没有签名,攻击者可自己伪造合法 nonce。

2. 加固与对抗(讲深度)

  • HMAC 而非裸 MD5/SHA:带密钥防彩虹台;app_secret 用更高等级保护(KMS、客户端白盒加密),密钥轮换机制
  • nonce 的存储成本:高 QPS 下 Redis 写压力大,可用布隆过滤器做初筛(少量误杀可接受,回查精确集合确认)
  • 幂等作为最后防线:重放即使穿透校验(TTL 边界、时钟偏移),业务层幂等键(订单号)保证不重复扣款——防重放是概率防御,幂等是确定性防御
  • HTTPS 是前提:签名方案不防抓包后的重放,但防不了密钥窃取;敏感操作叠加生物/二次认证
  • 证书绑定(certificate pinning)防中间人抓包,降低签名被截获分析的可能

3. 风控维度补充:同一签名特征高频重复请求(即使 nonce 都不同)→ 识别为脚本,触发验证码或限流。时间容忍窗口防时钟偏移(NTP 校时),建议 ±5 分钟内由 nonce 兜底。

一句话:签名防篡改、timestamp 限窗口、nonce 防重放、幂等兜底,四层递进。

💻 全栈开发专家 ​

重放的本质是攻击者拿合法签名的历史请求再发一遍,防线是"请求新鲜度 + 一次性随机数 + 一次性业务票据"。

核心机制:timestamp + nonce + 签名

客户端签名时必须包含时间戳和随机数:

text
sign = HMAC-SHA256(appSecret, method + path + sortedParams + body + timestamp + nonce)

网关校验三步:

  1. 验签:用同样算法重算比对(常量时间比较,防时序侧信道),防篡改。
  2. 时间窗:|now - timestamp| > 300s 直接拒绝——重放的老请求时间戳过期。
  3. nonce 一次性:nonce 存 Redis SETNX + 过期时间 = 时间窗长度:
java
Boolean first = redis.opsForValue().setIfAbsent("nonce:" + nonce, "1", 5, TimeUnit.MINUTES);
if (Boolean.FALSE.equals(first)) return reject("replay"); // 重复 nonce = 重放

时间窗内相同的 nonce 出现第二次即判定重放。窗口设 5 分钟是平衡:太短客户端时钟偏移会误杀,太长 Redis 存储压力大。

纵深防御

  • 传输层:全站 HTTPS/TLS 1.3,移动端证书锁定(certificate pinning)防中间人抓包。
  • 票据一次性:敏感操作(支付、改密)在签名之外再发一次性 token(用后即焚),抓到历史请求也没用。
  • 挑战应答:服务端先下发随机 challenge,签名包含 challenge,从根上杜绝"预生成签名重放"。

常见坑

  • 只签名 query 不签 body,body 被替换签名仍合法——签名要覆盖完整请求。
  • nonce 判重必须和时间窗校验同时开,只判 nonce 不限时间,攻击者可以提前囤积nonce。
  • Redis 挂掉时的降级策略要提前定(默认拒绝还是放行只验时间窗),别到线上才拍脑袋。