Js-Challenge是基于NJS模块的轻量级JS挑战机制,通过注入可执行JS逻辑验证客户端真实性,对静态爬虫有效但易被Puppeteer等工具绕过,防护强度低于Cloudflare。

Js-Challenge 是什么,它真能绕过爬虫?
Js-Challenge 不是 Nginx 原生功能,而是 ngx_http_js_module(NJS 模块)配合自定义 JavaScript 脚本实现的轻量级挑战逻辑。它不依赖外部服务,也不弹窗、不跳转,靠向客户端注入一段需执行的 JS 计算逻辑(比如解一道简单数学题或哈希校验),再验证响应头或 cookie 中的计算结果。对真实浏览器几乎无感,但多数静态爬虫、curl 或未启用 JS 执行的工具会直接失败。
注意:它不是验证码替代品,防护强度低于 Cloudflare 的 Bot Management,但胜在可控、低延迟、不侵入业务代码。
如何启用 NJS 并加载 Js-Challenge 脚本?
Nginx 必须编译时启用 --add-dynamic-module=nginx/modules/ngx_http_js_module,或使用官方预编译包(如 nginx-plus 或某些 Linux 发行版的 nginx-module-js 包)。运行时需在 nginx.conf 中显式加载:
load_module modules/ngx_http_js_module.so;
然后在 http 块中引入脚本:
js_import /etc/nginx/js/challenge.js;
脚本路径必须是绝对路径;challenge.js 需导出至少一个函数(如 js_challenge),供 location 中调用。
- 常见错误:
js_import放在server或location块里 → 报错 “js_import directive is not allowed here” - 权限问题:Nginx worker 进程需有读取
.js文件的权限,否则启动失败并报open() "/etc/nginx/js/challenge.js" failed (13: Permission denied) - 语法错误不会在 reload 时提示,只会在首次触发 JS 函数时记录到 error log,且错误信息极简(如 “JS exception”),建议先用
node challenge.js粗略验证逻辑
如何在 location 中触发挑战并放行合法请求?
典型做法是结合 js_set 和 if 判断,或更稳妥地用 js_content + 状态码控制流。推荐后者,避免 if 在 location 中的限制:
location /api/ {
js_content challenge.js_challenge;
}
对应 challenge.js 内部需处理三种情况:
- 请求带有效
X-Js-Validheader 或js_validcookie → 直接r.return(200, "ok")放行 - 请求无凭证 → 返回 401 + 内联 JS 脚本(含计算逻辑和
fetch回传结果),并设置短时效 cookie(如r.headersOut['Set-Cookie'] = 'js_pending=1; Max-Age=30; Path=/; HttpOnly; Secure') - 请求带 pending cookie 但无有效结果 → 返回 429 或重定向到挑战页(可选)
关键点:r.status = 401 后必须调用 r.return(),否则 Nginx 会返回空响应;JS 中不能直接读写 document,所有 DOM 操作由注入的前端脚本完成。
为什么挑战总被绕过?几个硬伤和缓解方式
Js-Challenge 的本质是“信任客户端执行结果”,所以天然存在缺陷:
- 自动化工具可通过 Puppeteer / Playwright 执行 JS 并提取结果 → 缓解:在 JS 中加入时间戳检查、随机 token 绑定、或要求执行后延迟若干毫秒再提交(服务端校验耗时)
- 攻击者复用一次通过的
X-Js-Validheader → 缓解:服务端生成一次性 token,验证后立即失效;或绑定 IP + User-Agent 哈希(注意隐私合规) - CDN 或反向代理缓存了 401 响应 → 缓解:确保
Cache-Control: no-store, no-cache在挑战响应头中明确设置 - 移动端 WebView 或老旧浏览器不支持注入脚本 → 建议 fallback 到简单 cookie 设置(如
js_ready=1),或允许首次请求静默通过(仅对 /api/ 下高频请求路径启用)
真正难绕过的不是 JS 本身,而是把挑战逻辑和业务请求频率、IP 行为、TLS 指纹等信号联动——但这已超出 Js-Challenge 单点能力,需要额外日志分析或集成 WAF。


















