SOP:Cloudflare 人机验证机制 + 给自己的网站上防爬
沉淀日期:2026-08-03 起因:看到 grok.com 的「正在进行安全验证」页面,想搞清楚这是什么、以及怎么给自己的站点用上 适用范围:个人开发者,站点多为 Next.js on Vercel(域名
*.saveme505.help,DNS 在 vercel-dns)+ 少量 Cloudflare Workers/Pages + tky VPS 反代
#第一部分:这东西到底是什么(概念辨析)
#1.1 四个容易混为一谈的产品
| 名称 | 本质 | 触发者 | 长什么样 | 需要 Cloudflare 代理域名吗 |
|---|---|---|---|---|
| Turnstile | 可嵌入任意网站的 CAPTCHA 替代组件(widget),开发者用 sitekey 主动集成 | 网站自己的前端代码 | 页面局部一个小框:转圈 / 复选框 / 完全不可见 | 不需要 |
| Challenge Pages(挑战页) | Cloudflare 在反向代理层拦截整个 HTTP 请求,返回全屏 HTML 拦截页,过关才放行到源站 | WAF 自定义规则 / Bot Management / Rate Limiting 的 action 选了 Managed Challenge | 全屏白页,带站点域名大标题,「正在验证您是否是真人」→「验证成功。正在等待 xxx 响应」 | 需要(橙云代理) |
| Bot Fight Mode / Super Bot Fight Mode | 域名级自动机器人打分 + 挑战,一键开关不写规则 | Cloudflare 仪表盘开关 | 触发时同样弹 Challenge Page | 需要 |
| Security Level / Under Attack Mode | 全局风险阈值旋钮;Under Attack 是极端临时档,几乎给所有访客发 JS 挑战 | 域名 Overview 页 Quick Action | 也走 Challenge Page 那套 UI | 需要 |
#1.2 三张截图的对应关系(本次调研的直接结论)
- 转圈「正在验证...」 → 非交互阶段。浏览器后台在跑 JS(环境采集、proof-of-work、PAT 校验),用户无需操作,几秒自动过。
- 复选框「请验证您是真人」 → 交互挑战。自动信号不够确定时,升级要求一次人工点击。
- 全屏「正在验证您是否是真人…验证成功。正在等待 grok.com 响应」 → Challenge Page,不是 Turnstile。
判别口诀:Turnstile 只是页面里的一小块,不会占满全屏,也永远不会说「正在等待 xxx 响应」——这句话本身就是代理层拦截的特征(请求还没到源站)。
为什么同一个站三种形态都能见到:判定是动态分级的。信号干净 → 静默转圈放行;风险居中 → 升级成复选框;风险高(数据中心 IP、VPN 出口、指纹异常、无历史声誉)→ 直接全屏拦截。grok.com 被吐槽验证死循环,社区反馈集中在 IP 信誉、浏览器扩展干扰、Cookie 异常,是 xAI 侧策略调得严,不是 Cloudflare 的锅。
#1.3 判定原理(从轻到重的四层)
- Private Access Token (PAT) —— iOS 16+/macOS Ventura+ 由系统级(Apple/运营商)匿名证明「这是真实设备」,不采集 PII,秒过。
- 浏览器环境 / TLS 指纹 —— Canvas、WebGL、UA、字体、时区一致性 + TLS 握手指纹 JA3/JA4(密码套件顺序、扩展顺序、GREASE 值)。Python
requests这类库的 JA3 早在黑名单里。 - Proof-of-Work —— 让浏览器跑一段哈希计算,正常几毫秒~几秒,难度随风险分动态调整(机房 IP 会拿到更难的题)。
- 行为信号 —— 鼠标轨迹、焦点、滚动、点击节奏喂给 ML 模型。第三方分析估算只对约 5% 的疑难流量触发。
#1.4 关键 Cookie(排障时要认得)
| Cookie | 作用 | 有效期 |
|---|---|---|
cf_clearance |
「已通过挑战」的凭证,签发一次后免重复验证。与生成时的 TLS 指纹 + IP + UA 绑定。属性 SameSite=None; Secure; Partitioned |
由 Challenge Passage 配置,业界观察默认约 30 分钟 |
__cf_bm |
Bot Management 的持续机器人评分/会话标识,几乎每次访问都刷新 | 30 分钟无操作后过期 |
__cfuvid |
Rate Limiting 用于区分共享 IP 背后的不同用户 | — |
__cflb |
负载均衡会话保持 | 几秒 ~ 24 小时可配 |
cf_chl_rc_* |
挑战平台内部诊断 | — |
二者区别:
cf_clearance= 通行证(一次性签发),__cf_bm= 持续监控评分。排障时cf_clearance损坏/过期是「验证死循环」的头号嫌疑。
#1.5 2025-2026 新动态
- Turnstile 早在 2023-09 就 GA,Managed 模式永久免费、官方未设请求量上限。
- Turnstile Analytics(2025-03):挑战次数、通过率、拦截率趋势。
- Pre-Clearance:让 SPA 用 Turnstile 主动签发
cf_clearance,后续 API 调用不会被全屏挑战页打断。 - Ephemeral ID(企业版):设备级跨会话异常关联,不依赖 IP。
- AI Labyrinth(2025-03):给不守 robots.txt 的 AI 爬虫喂 AI 生成的假内容迷宫,消耗其资源,同时把「跟随了隐藏链接」当强信号回灌模型。静默处理,不提示对方。
- Pay-per-crawl(2025,2025-12-10 增强):用 HTTP 402 对 AI 爬虫按请求收费,可设 Allow / Charge / Block。
- Content Signals Policy(2025-09):robots.txt 扩展语法,分别声明
search/ai-input/ai-train三种细分授权,已推广 380 万+ 域名,但纯自愿、无强制力。 - 2025-07-01 起,Cloudflare 新建站点的 robots.txt 默认对 AI 训练类爬虫设为 Block。
#第二部分:给自己的站点上防爬(决策与落地)
#2.1 先做决策:走哪条路
最关键的前提事实:
Cloudflare 的 Managed Challenge / WAF / Bot Fight Mode 必须让流量先经过 Cloudflare 边缘,也就是要求域名的权威 NS 在 Cloudflare(Full Setup)。 只把子域名 CNAME 指过去的 Partial/CNAME Setup 仅 Business/Enterprise 可用,Free 计划用不了。
而 Turnstile 完全不依赖 DNS 托管在哪——它是纯前端 widget + 后端 siteverify API 调用,站在 Vercel 上照样能用。
据此分两档:
#【轻量档】不动 DNS —— 推荐作为默认选择
适用:绝大多数 *.saveme505.help 子站(DNS 在 vercel-dns,且已有 HMAC 密码门)
- 关键接口挂 Turnstile(Invisible 或 Managed 模式):登录接口、注册/表单提交、任何会写库或花钱的 API
- 加 接口级限速:Upstash Redis / Vercel KV,例如登录接口每 IP 每分钟 5 次
- 需要时开 Vercel Firewall 的 Attack Challenge Mode(所有计划免费,仅在检测到异常流量时才对全站弹挑战);Vercel BotID 基础版免费,Deep Analysis(Kasada 驱动)需付费并在 Firewall → Rules 手动开启
- 公开静态站维持公开,只用
robots.txt+ Content Signals 声明 AI 训练授权
#【重装档】迁 NS 到 Cloudflare + 橙云代理
适用:tky VPS 反代的服务、流量敏感的 API 服务,或确实需要边缘层拦截的站点
- 域名 NS 迁到 Cloudflare(Full Setup,Free 计划可用)
- Vercel 侧保留自定义域名配置,Cloudflare 里对应记录开橙云
- Security → WAF → Custom rules 建规则,Action 选 Managed Challenge(官方推荐,优于 Interactive/JS Challenge)
- 对 webhook / 健康检查 / RSS 等路径显式加 Skip 规则
#2.2 套餐能力边界(别买错也别指望免费版)
| 能力 | Free | Pro | Business | Enterprise |
|---|---|---|---|---|
| Turnstile | ✅ 免费无限 | ✅ | ✅ | ✅ |
| WAF Custom Rules + Managed Challenge | ✅(条数有限,一般 5 条) | ✅ | ✅ | ✅ |
| Bot Fight Mode | ✅ | — | — | — |
| Super Bot Fight Mode | ❌ | ✅(Definitely Automated) | ✅(+ Likely Automated) | ✅ |
Bot Score cf.bot_management.score 字段 |
❌ | ✅ | ✅ | ✅ |
| 完整 Bot Management(自定义阈值/逐请求打分) | ❌ | ❌ | ❌ | ✅(加购) |
| CNAME/Partial Setup(不迁 NS) | ❌ | ❌ | ✅ | ✅ |
#2.3 ⚠️ Free 版 Bot Fight Mode 的大坑
Bot Fight Mode 是域名级一刀切,不能按路径豁免,且明确不能被 Skip 规则跳过(官方文档写死了)。这意味着一旦开启,你的 webhook、支付回调、健康检查、RSS 全都可能被拦。
结论:Free 计划下,宁可自己写 WAF Custom Rule + Managed Challenge(可精确控制路径、可 Skip),也别图省事开 Bot Fight Mode。
#第三部分:Turnstile 在 Next.js App Router 的接法
#3.1 机制要点
sitekey前端公开;secret key服务端私密,绝对不能加NEXT_PUBLIC_前缀- 前端拿 token → 传给自己后端 → 后端调
POST https://challenges.cloudflare.com/turnstile/v0/siteverify(application/x-www-form-urlencoded) - token 有效期 300 秒,且只能被验证一次。重复验证返回
timeout-or-duplicate - 三种模式:Managed(默认,风险高才弹交互)/ Non-Interactive(可见但不用点)/ Invisible(完全不可见)。表单场景推荐 Invisible 或 Managed
#3.2 环境变量
# .env.local
NEXT_PUBLIC_TURNSTILE_SITE_KEY=0x4AAA... # 公开
TURNSTILE_SECRET_KEY=0x4AAA... # 服务端私密,切勿加 NEXT_PUBLIC_ 前缀#3.3 客户端组件
// components/turnstile-widget.tsx
'use client';
import { Turnstile } from '@marsidev/react-turnstile'; // 社区封装,比手写 script 标签方便
export function TurnstileWidget({ onVerify }: { onVerify: (token: string) => void }) {
return (
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
options={{ theme: 'auto', size: 'flexible' }}
onSuccess={onVerify}
/>
);
}#3.4 服务端校验(Route Handler)
// app/api/contact/route.ts
import { NextRequest, NextResponse } from 'next/server';
async function verifyTurnstile(token: string, ip: string | null) {
const formData = new URLSearchParams();
formData.append('secret', process.env.TURNSTILE_SECRET_KEY!);
formData.append('response', token);
if (ip) formData.append('remoteip', ip);
// idempotency_key 可选:防止网络超时重试被误判为「token 重复使用」
formData.append('idempotency_key', crypto.randomUUID());
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: formData,
});
return res.json() as Promise<{ success: boolean; 'error-codes'?: string[] }>;
}
export async function POST(req: NextRequest) {
const body = await req.json();
const { turnstileToken, ...formFields } = body;
const ip = req.headers.get('x-forwarded-for')?.split(',')[0] ?? null;
const verification = await verifyTurnstile(turnstileToken, ip);
if (!verification.success) {
return NextResponse.json({ error: '人机验证失败' }, { status: 403 });
}
// 关键:verify 成功后立即处理业务逻辑,不要把 token 存起来复用
return NextResponse.json({ ok: true });
}#3.5 siteverify 错误码速查
| 错误码 | 含义 |
|---|---|
missing-input-secret / invalid-input-secret |
secret key 没传或不对 |
missing-input-response / invalid-input-response |
token 没传或不合法 |
bad-request |
请求格式错误 |
timeout-or-duplicate |
token 已过期(>300s)或已被验证过一次 —— 最常见 |
internal-error |
Cloudflare 侧内部错误,可重试 |
idempotency_key 陷阱:同一 token + 同一 key 重试是安全的;但不同 token 复用同一个 key,会拿到缓存的旧结果。
#3.6 避免 token 复用的三条纪律
- 一次表单提交只用一次 token;提交成功或失败后都在
onExpire/onError里 reset 组件重新挑战 - 服务端不要缓存已通过的 token 当「已验证白名单」
- 高价值接口(支付、注册)让 token 与业务数据在同一个 HTTP 请求体里一起提交,避免「先验证拿票据、再异步提交业务」的两段式设计被重放
#3.7 middleware 的取舍
middleware 里只做「是否携带有效 token/cookie」的粗筛和限速;真正的 siteverify 网络调用放 Route Handler——Edge Runtime 里 fetch 外部 API 有延迟和冷启动成本。
#第四部分:攻防现状(防守视角的威胁建模)
对手绕过 Cloudflare/Turnstile 的六种主流手法:
- Headless 特征规避 ——
undetected-chromedriver、Playwright/Puppeteer + stealth 抹除navigator.webdriver、CDP 痕迹、Canvas/WebGL 指纹 - TLS 指纹伪装 ——
curl-impersonate/curl_cffi(impersonate="chrome")精确复刻真实浏览器的 JA3/JA4 握手 - 住宅代理 / 蜂窝代理 —— 提升 IP 信誉分,规避机房 IP 的高风险判定
- 第三方打码服务 —— 2Captcha / Anti-Captcha / CapSolver 提交挑战参数,10-30 秒返回已解出的 token,成本约 1000 次 $1.45
- 倒卖
cf_clearancecookie —— 灰产维护已解出的 cookie 池,配合稳定的 IP+UA+TLS 组合复用到过期 - 注意:
curl-impersonate不执行 JS,遇到真正需要跑脚本的挑战仍得回退到真实浏览器/云端浏览器集群——这是 JS 挑战的价值所在
#防守方结论:不要依赖单点
「验证通过 = 一定是人」这个假设是错的。 打码服务能稳定拿到合法 token,验证码只能筛掉「懒惰的脚本」,筛不掉「愿意花钱雇人/AI 解码」的对手。
必须叠加:
- 三层纵深:DNS 层(WAF/Managed Challenge)+ 应用层(Turnstile 挂关键端点)+ 业务层(限速、签名、异常检测)
- 速率异常检测优先于验证码:同一 IP/Cookie/指纹短时高频命中同一接口,即使 token 验证通过也要限速
- 蜜罐字段 + 时间陷阱:不可见的 honeypot input(正常用户不会填)+「提交耗时 < 1-2 秒视为可疑」
- 高价值端点用签名/密钥而非验证码(见下节)
#第五部分:误伤与排障
#5.1 ⛔ 绝对不能加验证/挑战的路径
这些都是服务器对服务器调用,不具备执行 JS 的能力,加了等于业务静默失败:
- Webhook / 支付回调(Stripe、微信支付回调等)
- RSS / Atom feed(面向阅读器和聚合器)
- 已鉴权用户的 API(携带有效 session/JWT 的请求应基于 token 合法性放行)
- 健康检查 / uptime 监控端点(
/api/health、UptimeRobot 探活)——被拦会造成误报下线告警
做法:在 WAF Custom Rules 或 Vercel Firewall 里对这些路径显式加 Skip/放行规则,不要指望系统「聪明」识别。
#5.2 常见误伤群体
- 国内用户 / 机场代理 / 云服务器 IP —— IP 信誉分低,容易触发交互挑战甚至循环
- VPN 用户 —— 社区确认大量「无限循环」案例关掉 VPN 立刻解决
- 隐私浏览器 / 严格隐私扩展 / 广告拦截器 —— 会拦截或改写挑战所需 JS,导致脚本执行不完整卡死
- Cookie 异常 ——
cf_clearance损坏或过期导致反复重来 - 无障碍用户 —— Turnstile 宣称 WCAG 2.2 AA 合规,但社区仍反馈屏幕阅读器焦点陷阱、失败无语音反馈(未确认是否已修复,建议实测)
#5.3 「用户卡在验证循环」排查四步
- 清 Cookie/缓存,换隐身窗口重试
- 临时关闭 VPN/代理测试
- 检查 Cloudflare SSL/TLS 模式是否为 Full / Full Strict —— 排除「看起来像验证循环、实际是 HTTPS 重定向死循环」的情况
- 到 Security Events 用该访客的
cf-ray或 IP 查具体被哪条规则拦了,确认不是自己规则写错(比如全站 Challenge 而非仅敏感路径)
#5.4 ⚠️ Vercel + Cloudflare 橙云的两个已知坑
坑一:ERR_TOO_MANY_REDIRECTS
- 原因:Cloudflare SSL/TLS 模式设成 Flexible 时,浏览器→CF 是 HTTPS,但 CF→Vercel 源站发 HTTP;Vercel 强制 HTTPS 又把请求重定向回去,死循环。
- 解决:SSL/TLS 模式改成 Full 或 Full Strict。
坑二:证书卡在 "Failed to Generate Cert"
- 原因:Vercel 走 ACME 校验签发证书时,域名已开橙云,Cloudflare 会拦截或改写校验请求。
- 解决:先在灰云(仅 DNS)状态让 Vercel 签发好证书,确认域名验证通过后再切橙云。
- 泛域名证书场景下,代理模式与
_acme-challenge标签可能冲突,按 Cloudflare 官方 Partial Setup 指引处理。
#第六部分:监控与验证
#6.1 看哪里
- Cloudflare Security Events:Dashboard → Security → Events,每条请求被哪个产品拦截/挑战/标记,含触发规则、IP、UA、URI、国家
- Security → Analytics → Bot analysis:机器人流量占比
- Turnstile Analytics:Dashboard → Turnstile,每个 site key 的挑战次数/通过率/拦截率趋势
- Vercel Firewall 面板:Attack Challenge Mode 触发记录、BotID 拦截统计
#6.2 关键日志字段
| 字段 | 说明 |
|---|---|
cf-ray |
唯一请求 ID,让用户提供这个可精确定位那次请求 |
cf.bot_management.score |
1-99,越低越像机器人(需 Pro/Business 起步) |
cf.bot_management.verified_bot |
是否 Cloudflare 认证的良性爬虫(Googlebot 等) |
付费计划可通过 Log Explorer / Logpush 导出到自建 SIEM。
#6.3 自测方法
# 1. curl 裸打受保护路径 —— 应被拦截(返回挑战页或 403),而不是 200
curl -I https://your-domain.com/protected-path
# 2. 确认放行路径没被误伤
curl -I https://your-domain.com/api/health # 应该 200- 无头浏览器基线对比:先用无 stealth 的 Playwright 打一发(「裸机器人」基线),再用带 stealth 的打一发,对比两者结果,评估防护能挡住哪个层级的对手。
- 检查 Route Handler 日志里
siteverify的success字段在正常用户请求里符合预期,确认没误伤自己的流量。
#第七部分:针对我自己站点的落地清单
按当前架构(Vercel + vercel-dns + HMAC 密码门)的推荐动作:
- 静态展示站 / 导航站:不折腾,维持公开。只补
robots.txt+ Content Signals 声明 AI 训练授权 - 有登录/表单的动态站:现有 HMAC 密码门已是很强的第一层(未登录进不了内容页)。在此之上:
- 登录接口
/api/login加 Turnstile(Invisible 模式,不打扰) - 登录接口加 Upstash Redis 限速:每 IP 每分钟 5 次
- 任何写库/花钱的 API(生图、LLM 调用)加同样两层
- 登录接口
- 不需要迁 DNS——上面这些都不依赖 Cloudflare 代理
- 真要边缘防护的服务(tky VPS 反代、敏感 API):单独把该子域名 NS 迁 Cloudflare + 橙云 + WAF Custom Rules,严格按「先灰云验证书 → 再切橙云」的顺序
- 无论哪种方案:支付回调、健康检查、RSS 务必显式排除在验证规则之外
- 别开 Free 版 Bot Fight Mode(一刀切且不可 Skip)
#附:官方文档索引
Cloudflare 概念与配置
- Turnstile 总览 https://developers.cloudflare.com/turnstile/
- Turnstile Get Started https://developers.cloudflare.com/turnstile/get-started/
- Widget 三种模式 https://developers.cloudflare.com/turnstile/concepts/widget/
- 服务端校验 siteverify https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
- Turnstile 无限循环排查 https://developers.cloudflare.com/turnstile/troubleshooting/infinite-loops/
- Challenge Pages https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/
- Cloudflare Cookies 说明 https://developers.cloudflare.com/fundamentals/reference/policies-compliances/cloudflare-cookies/
- WAF Custom Rules - Skip https://developers.cloudflare.com/waf/custom-rules/skip/
- WAF - Challenge bad bots https://developers.cloudflare.com/waf/custom-rules/use-cases/challenge-bad-bots/
- Under Attack Mode https://developers.cloudflare.com/fundamentals/reference/under-attack-mode/
- Super Bot Fight Mode (Pro) https://developers.cloudflare.com/bots/get-started/pro
- Bot Management 变量 https://developers.cloudflare.com/bots/reference/bot-management-variables
- Bot Analytics https://developers.cloudflare.com/bots/bot-analytics/
- DNS Partial (CNAME) Setup https://developers.cloudflare.com/dns/zone-setups/partial-setup/
- AI Labyrinth https://developers.cloudflare.com/bots/additional-configurations/ai-labyrinth/
- Pay-per-crawl changelog https://developers.cloudflare.com/changelog/2025-12-10-pay-per-crawl-enhancements/
- Turnstile GA 公告 https://blog.cloudflare.com/turnstile-ga/
- 控制内容用于 AI 训练 https://blog.cloudflare.com/control-content-use-for-ai-training/
Vercel 侧
- BotID Get Started https://vercel.com/docs/botid/get-started
- Attack Challenge Mode https://vercel.com/docs/vercel-firewall/attack-challenge-mode
- Rate Limiting SDK https://vercel.com/docs/vercel-firewall/vercel-waf/rate-limiting-sdk
- 解决 Cloudflare 代理导致的 err_too_many_redirects https://vercel.com/kb/guide/resolve-err-too-many-redirects-when-using-cloudflare-proxy-with-vercel
- 社区:Failed to Generate Cert https://community.vercel.com/t/custom-domain-stuck-on-failed-to-generate-cert-cloudflare-proxy/22662
Next.js 集成示例
- nextjs-turnstile 示例仓库 https://github.com/davodm/nextjs-turnstile
- marsidev/react-turnstile 服务端校验 https://deepwiki.com/marsidev/react-turnstile/3.2-server-side-validation
攻防参考(防守方威胁建模用)
- curl_cffi 绕过指南 https://www.blog.datahut.co/post/web-scraping-without-getting-blocked-curl-cffi
- Scrapfly: 如何绕过 Turnstile https://scrapfly.io/blog/posts/how-to-bypass-cloudflare-turnstile
- cf-clearance 项目 https://github.com/vvanglro/cf-clearance
社区排障帖
- grok.com 验证问题 https://community.cloudflare.com/t/grok-com-verifying-you-are-human-this-may-take-a-few-seconds/794493
- VPN 导致验证循环 https://community.cloudflare.com/t/infinite-loop-solved-by-disabling-vpn/658027
来源:沉淀/SOP-Cloudflare人机验证与网站防爬.md(整理于 2026-08-18)