在 Nginx 中实现 API 防重放的核心是验证请求的时效性与唯一性,需基于 OpenResty + Lua + Redis:提取 X-Timestamp 和 X-Nonce 头,校验时间戳是否超时(如 >30 秒),再通过 Redis SET NX EX 原子写入 nonce(有效期 300 秒),失败即返回 403。

在 Nginx 中实现 API 接口防重放,核心是验证请求的**时效性 + 唯一性**,需借助 OpenResty 和 Lua 模块,在 location 块中嵌入逻辑完成校验。纯原生 Nginx 不支持 nonce 存储与时间判断,必须依赖 ngx_lua 与外部存储(如 Redis)协同工作。
启用 Lua 环境并配置 Redis 连接
确保已安装 OpenResty(含 ngx_lua、redis lua 库),并在 http 块中预定义 Redis 句柄:
- 在
http块顶部添加全局 Redis 配置(复用连接池,避免每次新建):
upstream redis_backend { server 127.0.0.1:6379; }
在目标 location 中写 access_by_lua_block 校验逻辑
仅对需要防护的接口路径启用,例如 location /api/v1/submit:
- 提取请求头中的
X-Timestamp和X-Nonce(约定前端必须携带) - 检查时间戳是否超时(如:当前时间 − 时间戳 > 30 秒 → return 403)
- 用 Redis 的
SET NX EX原子写入 nonce,有效期设为略大于时间窗口(如 300 秒) - 写入失败(nonce 已存在)即判定为重放,直接返回 403
access_by_lua_block {
local headers = ngx.req.get_headers()
local ts = headers["X-Timestamp"]
local nonce = headers["X-Nonce"]
if not ts or not nonce then ngx.exit(403) end
if tonumber(ngx.time()) - tonumber(ts) > 30 then ngx.exit(403) end
local red = require "resty.redis":new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then ngx.log(ngx.ERR, "redis connect err: ", err) ngx.exit(500) end
local res, err = red:set("nonce:" .. nonce, "1", "EX", 300, "NX")
if not res or res == ngx.null then ngx.exit(403) end
}
proxy_pass http://backend;
}
配合前端约定与安全加固
防重放不是单靠 Nginx 完成的,需前后端协同:
- 前端每次请求必须生成唯一 nonce(如 UUIDv4)和精确 Unix 时间戳(秒级或毫秒级)
- 建议签名参与 nonce 和 timestamp 计算(如 HMAC-SHA256(nonce + timestamp + secret)),防止篡改头字段
- Nginx 层应隐藏版本号(
server_tokens off)、关闭不必要方法(limit_except POST {})、限制请求体大小(client_max_body_size 2m) - Redis 故障时可降级为仅校验时间戳(临时放宽策略),避免全站不可用
验证与可观测性
上线前务必验证逻辑有效性:
- 用 curl 发送相同 nonce + 旧 timestamp → 应返回 403
- 发送相同 nonce + 新 timestamp → 仍应返回 403(nonce 已被 Redis 记录)
- 不同 nonce + 合法 timestamp → 正常透传
- 开启
ngx.log(ngx.WARN, "anti-replay hit for ", nonce)日志,便于追踪异常模式
不复杂但容易忽略细节,关键在时间同步、nonce 唯一性保障和 Redis 写入原子性。


















