nginx + lua 动态挑战码验证通过滑动窗口计数、请求特征识别和轻量前端验证实现毫秒级人机校验,拦截可疑请求后发放10分钟加密凭证,兼顾防护强度与用户体验。

用 Nginx + Lua 实现动态挑战码验证防 CC 攻击,核心是“先拦截可疑请求,再触发人机验证”,而不是等攻击打满才响应。它比单纯限流更友好,也比静态黑名单更实时。
识别需要挑战的请求
不是所有请求都走验证,得先判断哪些行为异常。常见策略包括:
- 单 IP 在 60 秒内对敏感路径(如 /login、/api/search)发起超过 15 次请求
- 请求头缺失 User-Agent 或值为已知爬虫特征(如 python-requests、curl)
- URI 中含高频扫描特征(如 /phpmyadmin、/wp-admin、大量 .php?xxx= 参数)
- Referer 为空且请求方法为 POST(可能绕过前端校验)
用 Lua 记录并判定风险等级
借助 OpenResty 的 lua_shared_dict 在内存中维护滑动窗口计数器,避免每次查 Redis 增加延迟:
- 定义共享字典:lua_shared_dict req_counter 10m;
- 在 access_by_lua_block 中读取 IP+URI 组合键,递增计数并设置 60 秒过期
- 若计数 ≥ 阈值,调用 ngx.exit(429) 并跳转到验证 location(通过 error_page 429 = @challenge)
返回轻量级前端验证页面
不依赖后端渲染,由 Nginx 直接返回含 Turnstile 或 reCAPTCHA v3 的 HTML 片段:
- 在 @challenge location 中设 Content-Type: text/html
- 内联加载 Cloudflare Turnstile 脚本,并配置 data-callback 提交 token 到 /verify-challenge
- 页面带隐藏字段记录原始请求路径与时间戳,供后续校验使用
验证通过后发放临时通行凭证
用户提交 token 后,Lua 脚本调用 Cloudflare API 校验有效性:
- 验证成功则写入一个加密 cookie(如 ch_valid=xxx),有效期 10 分钟
- 同时在 shared_dict 中标记该 IP 为“已验证”,后续 5 分钟内跳过计数
- 重定向回原始请求路径,或直接 proxy_pass(需检查 cookie 是否有效)
整个流程不落磁盘、不依赖外部服务(除验证 API),响应在毫秒级,既能拦住脚本型 CC,又不影响真实用户连续操作。











