单纯用时间戳校验不可靠,因攻击者可在时间窗口内立即重放合法请求,且客户端时钟偏差易致误拒;必须结合nonce实现唯一性绑定,通过HMAC-SHA256签名验证请求新鲜性与合法性,并用Redis缓存带TTL的nonce以高效防重放。

为什么单纯用时间戳校验不够可靠
服务端收到请求时检查 timestamp 是否在 5 分钟内,看似能防重放,但实际容易被绕过:攻击者可截获合法请求后立即重发,只要网络延迟低,时间戳依然有效;客户端本地时钟不准还会导致大量误拒。关键在于——时间窗口是被动防御,缺乏请求唯一性绑定。
真正有效的做法是把时间戳和一次性随机值(nonce)组合哈希,并要求服务端缓存已处理过的 nonce 值(带 TTL)。这样即使时间戳合法,重复的 nonce 也会被直接拒绝。
如何生成和验证 nonce + timestamp 签名
签名不是为了加密数据,而是证明“这个请求此刻由合法客户端发出且未被复用”。推荐用 HMAC-SHA256 对拼接字符串做摘要:
-
message = timestamp + "|" + nonce + "|" + request_body(若无 body 可省略) - 客户端用共享密钥
API_SECRET计算hmac.new(key, message.encode(), hashlib.sha256).hexdigest() - 服务端收到后,用相同逻辑重算并比对
signature字段,不一致则拒绝 -
timestamp必须随请求传入(如 HTTP HeaderX-Timestamp),不能只靠服务端取当前时间
服务端怎么安全存储和清理已用 nonce
不能用内存字典或普通数据库主键去查 nonce 是否存在——高并发下会成为瓶颈,且无法自动过期。必须依赖带 TTL 的缓存系统:
立即学习“Python免费学习笔记(深入)”;
- Redis 是首选:
redis.setex(nonce, 300, "1")(300 秒后自动删除) - 插入前先用
redis.exists(nonce)判断,存在即说明已被用过,直接 401 - 避免用 UUID 或纯数字做
nonce,应使用密码学安全随机数:secrets.token_urlsafe(16) - 不要把
nonce存进日志或审计表,防止泄露后被构造重放请求
Python Flask/FastAPI 中的典型集成陷阱
很多人在中间件里写完校验逻辑就以为万事大吉,结果上线后发现部分请求频繁失败。常见问题集中在时钟同步和签名覆盖上:
- 客户端和服务端系统时间差超过 5 分钟?别只依赖 NTP,应在 API 响应头中返回服务端当前
X-Server-Time,让客户端动态校准 - 签名计算是否包含 query string?必须统一约定:Flask 的
request.url和 FastAPI 的request.url表现不同,建议固定用request.method + request.url.path + sorted_query_string - Body 被中间件读取一次后不可重复读(如
await request.body()),签名计算必须在解析 body 前完成,否则后续业务逻辑拿不到数据 - 不要在签名里包含 Cookie 或 Authorization 头——它们本身可能含敏感信息,且不属于请求“内容”范畴
重放防护本质是状态协同:客户端要生成不可预测的一次性凭证,服务端要快速判定其新鲜性。任何环节跳过状态检查(比如为性能绕过 nonce 查 Redis),就等于把门锁换成了贴纸。


















