Nginx层结合Redis实现Nonce校验是防重放攻击的轻量高效方案:客户端生成UUID v4作为Nonce并携时间戳,Nginx通过Lua调用Redis执行原子性SETNX+EX校验,成功则放行,失败即拒绝,确保请求唯一性与时效性。

在 Nginx 层结合 Redis 实现 Nonce 随机数校验,是防止重放攻击(Anti-replay)一种轻量、高效且可落地的方案。核心思路是:每个请求携带一个一次性、有时效性的随机标识(Nonce),Nginx 在转发前通过 Lua 脚本查 Redis,若该 Nonce 已存在则拒绝请求,否则写入并设置过期时间。
如何生成和传递 Nonce
Nonce 必须满足唯一性、不可预测性和时效性。推荐由客户端生成 UUID v4(如 uuid.uuid4().hex),并通过标准 HTTP 头(如 X-Request-Nonce)或 URL 参数(不推荐敏感场景)传递。服务端不生成、不修改,只做校验——这样可避免时钟同步、服务端状态耦合等问题。
- 客户端每次请求前调用
generate_nonce(),确保无重复; - 建议同时传入时间戳(如
X-Request-Timestamp),用于后续校验请求是否“过老”(例如 > 30 秒); - Nonce 不应与用户身份强绑定(如拼接 uid),否则会破坏其“一次性”语义,影响并发请求。
Nginx + Lua 脚本校验逻辑
需使用 OpenResty(含 ngx_lua 模块)或已编译 Lua 支持的 Nginx。在 location 块中嵌入 access_by_lua_block,完成三步操作:
- 提取请求头中的 Nonce 和 Timestamp;
- 检查 Timestamp 是否超时(如
os.time() - tonumber(timestamp) > 30); - 调用 Redis 的
SETNX(或SET ... NX EX)尝试写入 Nonce,成功即为新请求,失败说明已被消费。
示例关键 Lua 代码片段:
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
red:connect("127.0.0.1", 6379)
<p>local nonce = ngx.var.http_x_request_nonce
local ts = ngx.var.http_x_request_timestamp</p><p>if not nonce or not ts or os.time() - tonumber(ts) > 30 then
ngx.exit(ngx.HTTP_BAD_REQUEST)
end</p><p>local ok, err = red:setex(nonce, 300, "1") -- 5分钟有效期
if not ok then
ngx.exit(ngx.HTTP_FORBIDDEN) -- 已存在或 Redis 异常
endRedis 存储设计要点
无需复杂结构,直接用字符串键值对即可,Key 是 Nonce,Value 可固定为 "1" 或空字符串。重点在于原子性与过期控制:
- 必须使用
SETEX或SET ... NX EX,避免先EXISTS再SET的竞态条件; - 过期时间(TTL)建议设为业务单次请求最大生命周期的 2–3 倍(如 300 秒),兼顾安全与容错;
- 不建议用 Hash 或 Sorted Set 管理 Nonce,增加复杂度且无收益;
- Redis 实例需启用
protected-mode no(仅内网可信环境),并限制防火墙仅允许 Nginx 所在主机访问 6379 端口。
配套增强措施
单一 Nonce 校验能防基础重放,但实际生产中建议叠加以下策略提升鲁棒性:
- 与 IP + User-Agent 组合限频(用 Lua + Redis INCR + EXPIRE),识别高频重试行为;
- 对敏感接口(如登录、支付)强制要求 TLS 1.2+,防止中间人截获并重放;
- 在响应头中返回
X-Nonce-Used: true或签名摘要,便于前端调试与审计; - 记录被拦截的重放请求日志(含 IP、Nonce、时间),接入 SIEM 做关联分析。


















