前言:这份指南能给你带来什么

Cloudflare Turnstile 就是那个带旋转圆圈、显示「Verifying」的小组件,如今越来越多地取代了传统的图片验证码。它出现在注册表单、电商购物车、落地页和各类服务的个人后台中。普通访客几乎察觉不到它的存在,但对通过移动代理工作、每天要打开几十个网站的营销人员、投手或开发者来说,它却成了一道谜题。为什么有的配置文件一秒钟就通过了验证,而有的却会卡住、要求勾选复选框,甚至返回错误代码?

本指南将从两个角度拆解 Cloudflare Turnstile。作为网站所有者,你会亲手把组件接入页面、配置服务端验证、学会读懂统计数据。作为访客,你会明白 Turnstile 究竟收集哪些信号、能看到你浏览器、网络和代理的哪些信息,以及为什么会做出不同判断。这些知识对两类人同样有用:一类想保护自己的表单免受机器人侵扰,另一类希望自己的操作配置文件在验证时看起来就是普通用户。

适合哪些人阅读

  • 业务所有者和营销人员,垃圾提交和虚假注册影响了统计、消耗了预算。
  • 开发者,需要快速、无差错地把 Turnstile 集成进表单,并在服务端正确验证令牌。
  • 投手和多账号运营人员,通过移动代理工作,想弄明白 Turnstile 会看到自己连接中的什么。

需要提前掌握的知识

不需要特殊知识。只要你知道什么是 HTML 页面、会打开浏览器开发者工具、对任意一种服务端语言(PHP、Node.js、Python)有最基本了解即可。即使你没有服务端,也能完成本指南的大部分内容:组件可以接入并在本地页面上测试。

需要花多少时间

完整走一遍大约需要 1.5 到 2 小时。注册和创建组件约占 10-15 分钟,接入页面 20 分钟,服务端验证 30-40 分钟,测试和排查再来 30 分钟。理论部分可以按任意顺序阅读,需要时再回头查阅。

前置准备:工具和权限

开始前请把所需材料准备齐全,这样能避免中途卡壳。

必要的工具和权限

  • Cloudflare 账号。 免费。用邮箱几分钟就能注册好。不需要把域名托管到 Cloudflare——Turnstile 在任何网站、任何主机上都能运行。
  • 网站或测试页面。 任何带表单的 HTML 页面都可以。本地实验用一个文件、通过简单的本地服务器打开就够了。
  • 服务端环境。 任何支持 PHP 的主机,或者你本机上的 Node.js、Python。用于指南的后半部分——验证令牌。
  • 现代浏览器。 最新版本的 Chrome、Firefox、Edge 或 Safari,并打开开发者工具。
  • 可切换 IP 的移动代理。 排查部分会用到,用来观察组件在不同网络和地址轮换下的反应。

系统要求

Turnstile 要求不高。组件能在任何支持现代 JavaScript 的浏览器中运行,并从 challenges.cloudflare.com 域名加载代码。如果你的网络或浏览器扩展屏蔽了这个域名,组件就不会加载——测试时要留意这一点。服务端验证需要能够发起 HTTPS 出站请求。

开始前要准备的事项

  1. 新建一个文本文件用来做笔记。你会把 site key、组件名称、主机名列表和测试结果记在里面。
  2. 打开你想要保护的表单页面,保存一份带日期标记的副本。这是你的备份。
  3. 如果你有服务端表单处理程序,也复制一份。我们要往里面加验证代码。
  4. 确认你的本地或线上服务器通过 HTTPS 或 localhost 提供页面。Turnstile 在普通 HTTP 下也能工作,但线上网站无论如何都需要 HTTPS。

⚠️ 注意: 组件的密钥绝不能存在 HTML、页面 JavaScript 或公开仓库中。它只存在于服务端。如果你不小心泄露了它,立刻在 Cloudflare 面板中重新签发密钥——旧密钥会失效。

基础概念:Cloudflare Turnstile 的运作原理

为了后续步骤更好理解,这里用简单的语言拆解几个关键术语。

关键术语

  • 组件(Widget) ——访客看到的那个方块。技术上是 Cloudflare 域名加载的 iframe,嵌入在你的页面中。
  • Site key ——组件的公开标识符。它写入 HTML 中,所有人都能看到。Cloudflare 靠它判断要渲染哪个组件、允许用于哪些域名。
  • Secret key ——私密密钥。你的服务端用它来确认收到的令牌是真的。永远不离开服务端。
  • 令牌(Token) ——验证成功后组件生成的字符串。它被写入表单的隐藏字段,随其他数据一起发送到你的服务端。
  • Siteverify ——Cloudflare 的接口,服务端把令牌和密钥发过去,收到成功与否的响应。
  • 组件模式 ——展示方式:托管式(Managed)、非交互式(Non-interactive)或不可见式(Invisible)。区别见下文。
  • 主机名(Hostname) ——允许使用组件的域名。如果域名未列出,组件会报错拒绝运行。

四句话讲清工作原理

  1. 页面加载 Turnstile 脚本,组件在浏览器中悄悄运行一系列检测。
  2. Cloudflare 收集结果,连同网络数据一起评估,决定是直接放行、显示复选框要求确认,还是拒绝。
  3. 成功时组件生成一次性令牌,并填入表单。
  4. 你的服务端收到表单,把令牌发往 siteverify,只有收到肯定响应才处理提交。

Turnstile 到底检测什么——整体图景

关键要明白一点:Cloudflare Turnstile 不是靠图片谜题来判断「人类程度」。它评估的是环境一致性:浏览器、网络和行为能否构成一幅可信的画面。主要的信号类别:

  • 浏览器环境。 脚本执行一系列小型 JavaScript 任务,检查环境是否像真正的浏览器:窗口对象的结构、图形渲染方式、是否存在自动化痕迹、声明的 User-Agent 与引擎真实能力是否一致。
  • 工作量证明。 组件要求浏览器做少量计算。对人类是零点几秒的事,对每天打开成千上万页面的机器人却是实实在在的负担。
  • 网络信号。 请求来源 IP 地址和自治系统的声誉、网络特征是否与声明的浏览器匹配、该地址在 Cloudflare 全网范围内的请求历史。
  • 设备信任令牌。 在 Apple 设备和某些其他生态中,Turnstile 可以向操作系统请求确认这是真实设备——这样验证就完全不需要计算。
  • 页面行为。 组件出现的时机、表单提交的时刻、托管模式下的动作自然度。

Turnstile 不做的事:不收集用于广告画像的数据、不通过第三方 cookie 跨站追踪用户、从不显示图片拼图。这无论从个人数据法角度还是从转化率角度看都很重要——访客不会因烦人的验证码而离开。

三种组件模式

  • Managed(托管式) ——默认模式。组件可见,转动指示器,如果存疑就显示需要点击的复选框。适合大多数表单。
  • Non-interactive(非交互式) ——组件可见,但从不要求操作。要么自行通过,要么报错。适合不能容忍多余点击的页面。
  • Invisible(不可见式) ——组件完全不渲染。验证在后台进行。适合不想改变设计的按钮和表单,但需要小心处理错误。

第 1 步:弄清 Turnstile 从你这边能看到什么

本阶段目标:在开始配置之前,你必须理解组件会获取你环境的哪些数据。这是后续排查、以及有意识地通过移动代理工作的基础。

亲眼观察组件的工作

  1. 打开任何部署了 Cloudflare Turnstile 的网站。这类组件很容易辨认——方块右下角有 Cloudflare 标志,还有「Privacy」和「Terms」链接。
  2. 按 F12 或右键选择「检查」,打开开发者工具。
  3. 切到「Network」(网络)标签,刷新页面。
  4. 在筛选框输入 challenges.cloudflare.com。你会看到几个请求:api.js 的加载、组件 iframe 的加载,以及一个或多个 POST 请求——这就是检测结果的发送。
  5. 打开「Elements」(元素)标签,找到 class 为 cf-turnstile 的方块。验证成功后,内部会出现一个隐藏的 input 字段,name 为 cf-turnstile-response,值为一长串字符串。那就是令牌。

建议: POST 请求的内容经过加密和混淆,读它没用。关注别的:发出了多少个请求、验证花了多长时间、组件状态是否变成「Success」。这才是你外部可见的信任指标。

Turnstile 能看到你浏览器的什么

组件直接在你的窗口中执行 JavaScript,所以凡是页面脚本能访问的,它也能访问:引擎版本、已安装的 API、屏幕尺寸、时区、界面语言、图形和字体渲染特点、那些在自动化浏览器中常被覆写的函数行为。它不读取你的文件,也不窥探其他标签页。但它非常擅长发现浏览器「说一套做一套」。比如 User-Agent 声称是「Android 上的 Chrome」,可环境里没有触摸事件,却有一些移动设备上不会有的 API。

Turnstile 能看到你网络的什么

这里对通过移动代理工作的人来说最有趣。组件的所有请求都发往 Cloudflare 服务器,也就是说 Cloudflare 能看到你的外部 IP 地址、它的自治系统(即运营商)、国家,以及连接的低层特征——你的客户端如何建立加密连接。不同浏览器的这些特征不同,Cloudflare 会把它们与声明的 User-Agent 对比。

移动运营商的地址来自大型共享池,同一个地址上同时有数百名真实用户。所以这类地址本身声誉中性甚至良好——封禁它们就等于封禁真实的人。但声誉只是信号之一。如果从移动地址过来的是一个网络指纹属于桌面脚本的浏览器,时区在另一个大洲,还带自动化痕迹,那么画面就对不上,组件会切到交互模式或直接拒绝。

Turnstile 能看到你行为的什么

在托管模式下,组件会关注表单在加载后多快被提交、用户是否与页面有过互动、点击复选框是否自然。在不可见和非交互模式下,行为因素很少——判断主要基于环境和网络。

✅ 检查: 到这一步你应该能打开 Network 标签、筛选发往 challenges.cloudflare.com 的请求、看到隐藏字段 cf-turnstile-response,并能用自己的话说出三类信号:浏览器、网络、行为。如果做到了,就可以进入创建自己的组件了。

可能遇到的问题

  • 完全没有发往 challenges.cloudflare.com 的请求。 很可能是浏览器扩展或企业过滤器屏蔽了该域名。测试期间先关掉屏蔽器。
  • 组件一直停在验证状态。 检查电脑的系统时间:与实际时间偏差过大验证会失败。

第 2 步:在 Cloudflare 面板中创建组件

本阶段目标:拿到一对密钥——site key 和 secret key——并正确配置域名列表和运行模式。

  1. 打开 Cloudflare 控制面板并登录。如果没有账号,点「Sign up」,输入邮箱和密码,确认邮件。
  2. 在左侧菜单找到 Turnstile 项。如果你有多个账号,先在主页选对账号。
  3. 点击蓝色按钮 Add widget(添加组件)。
  4. 在 Widget name 字段输入一个清楚的名称,比如「落地页表单—主站」。名字只有你自己看得到,但有十来个组件时它能救你于混乱。
  5. 在 Hostname management 区块点击 Add hostnames,输入组件要运行的域名。不带协议、不带路径:example.ru,而不是 https://example.ru/form。子域名要单独添加,或指定根域名——那样子域名也被允许。
  6. 本地测试要把 localhost 加入列表。这是官方支持的,不影响线上运行。
  7. 在 Widget Mode 区块选择模式。第一次选 Managed——这样你能看到组件的所有状态,包括交互状态。
  8. Pre-clearance 选项先关掉。它只在网站经过 Cloudflare 代理时才用,进阶部分会讲。
  9. 点击 Create。
  10. 下一屏你会看到两个字段:Site Key 和 Secret Key。把它们都复制到笔记文件。密钥之后能在组件设置里查看,但立即保存更方便。

建议: 一次性创建两个组件——一个给线上域名,一个叫「测试」、主机名为 localhost。这样你可以随意试验模式和设置,不动工作组件的数据。

正确的成果长什么样

Turnstile 列表中会出现一张卡片,显示组件名称、模式和主机名列表。Site key 以「0x」开头,约 24 个字符,secret key 同样以「0x」开头但更长。如果密钥看起来不一样,你多半复制错了字段。

✅ 检查: 你的笔记文件里记录了 site key、secret key、组件名称、主机名列表和所选模式。Cloudflare 面板中组件显示为激活状态。

可能遇到的问题

  • Create 按钮不可点。 没添加任何主机名,或者输入有误(带协议、斜杠、空格)。
  • 菜单里找不到 Turnstile。 你在某个具体域名的设置里。回退到账号层级——Turnstile 在那里,而不是域名区域内。

第 3 步:把组件接入带表单的页面

本阶段目标:组件在页面上显示、通过验证、把令牌填入表单。

接入脚本

  1. 在编辑器中打开带表单的 HTML 文件。
  2. 在 head 标签内或 body 闭合标签前添加脚本引用行:
<script src='https://challenges.cloudflare.com/turnstile/v0/api.js' async defer></script>

async 和 defer 属性让页面不等待脚本加载。组件会稍后出现,但用户不会感到内容加载延迟。

放置组件容器

  1. 找到你要保护的表单。通常是包含姓名、邮箱、电话字段的 form 标签。
  2. 在提交按钮正前方插入一个带 cf-turnstile class 和你的 site key 的空方块:
<form action='/submit.php' method='POST'> <input type='text' name='name' placeholder='你的名字'> <input type='email' name='email' placeholder='邮箱'> <div class='cf-turnstile' data-sitekey='你的_SITE_KEY' data-theme='light'></div> <button type='submit'>提交</button> </form>
  1. 把 你的_SITE_KEY 替换成笔记中的密钥。密钥不能填在这里。
  2. 保存文件,通过 localhost 在浏览器中打开页面。

你应该看到什么

加载后一两秒,方块处会出现一个约 300×65 像素的组件。它先显示加载指示器和「Verifying」文字,然后是绿色勾和「Success」。如果 Cloudflare 决定复查环境,会出现一个带「Verify you are human」文字的复选框——点它,片刻后组件显示成功。

打开开发者工具的 Elements 标签,展开 cf-turnstile 方块。里面出现了 name 为 cf-turnstile-response 的隐藏 input。它的值就是那串长令牌。表单提交时,正是它会被发送到服务端。

容器的实用属性

  • data-theme ——light、dark 或 auto。auto 会跟随用户系统主题。
  • data-size ——normal、compact 或 flexible。flexible 让组件按容器宽度拉伸——适合移动端布局。
  • data-language ——语言代码,比如 ru。默认情况下组件采用浏览器语言。
  • data-action ——短标签,比如 login 或 checkout。它会在 siteverify 响应中返回,帮助在统计中区分表单。
  • data-callback ——成功后调用的 JavaScript 函数名。令牌会传给它。
  • data-error-callback ——出错时接收错误码的函数。
  • data-refresh-expired ——令牌过期时怎么办:auto 自动重新请求,manual 显示刷新按钮,never 什么都不做。

建议: 立即加上 data-error-callback,把错误码输出到控制台。Turnstile 的错误码很信息量大:110xxx 系列表示密钥或域名问题,300xxx 表示浏览器执行失败,600xxx 表示验证未通过。没有它,你只能猜组件为什么沉默。

替代方案:通过 JavaScript 显式渲染

如果你用框架开发,或者想控制组件出现的时机,就用显式渲染替代隐式渲染。给脚本地址加参数 render=explicit,并调用带参数的 turnstile.render:

turnstile.render('#my-widget', { sitekey: '你的_SITE_KEY', theme: 'auto', action: 'signup', callback: function(token) { console.log('令牌已获取', token.length); } });

这种方式允许出错后用 turnstile.reset 方法重绘组件,用 turnstile.getResponse 方法获取当前令牌。

✅ 检查: 组件在页面上显示「Success」,DOM 中存在带令牌的 cf-turnstile-response 字段,控制台没有错误。试着刷新页面三四次——每次都应出现新令牌。

可能遇到的问题

  • 组件显示错误 110200。 打开页面的域名未加入组件的主机名列表。确认你是通过 localhost 打开的,而不是 127.0.0.1 或 file://——它们是不同的主机名。
  • 组件不出现,控制台空白。 脚本没加载。检查脚本地址有无拼写错误、是否被屏蔽器拦截。
  • 组件破坏布局。 使用 data-size='flexible',或把方块包进所需宽度的容器。

第 4 步:配置服务端令牌验证

本阶段目标:服务端拒绝任何没有有效令牌的表单提交。这是最重要的一步——没有它,组件只是装饰品,因为机器人可以绕过页面直接发 POST 请求。

siteverify 请求的结构

你的服务端向 https://challenges.cloudflare.com/turnstile/v0/siteverify 发 POST 请求,字段包括:

  • secret ——你的密钥;
  • response ——来自 cf-turnstile-response 字段的令牌;
  • remoteip ——访客 IP,可选,但有用;
  • idempotency_key ——可选的请求唯一标识符,进阶部分会讲。

响应是 JSON。关键字段:

{ "success": true, "challenge_ts": "2026-03-14T10:22:31.000Z", "hostname": "example.ru", "error-codes": [], "action": "signup", "cdata": "" }

令牌有效期为 300 秒,且一次性。重复验证同一令牌会返回 timeout-or-duplicate 错误。

PHP 示例

  1. 打开表单处理文件,比如 submit.php。
  2. 在最开头,处理任何表单数据之前,加入验证块:
<?php $token = $_POST['cf-turnstile-response'] ?? ''; if ($token === '') { http_response_code(400); exit('验证未通过:没有令牌'); } $data = [ 'secret' => getenv('TURNSTILE_SECRET'), 'response' => $token, 'remoteip' => $_SERVER['REMOTE_ADDR'] ]; $ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $raw = curl_exec($ch); curl_close($ch); $result = json_decode($raw, true); if (empty($result['success'])) { http_response_code(403); exit('验证未通过:' . implode(',', $result['error-codes'] ?? ['no-response'])); } 

关于作者

Roman Melnikov

Roman Melnikov

Technical Writer and System Administrator

工作经验: Technical writer and DevOps engineer with 9 years of experience. Created over 50 detailed guides on system configuration and administration. His instructions helped thousands of professionals successfully solve technical tasks. Popular author on Habr and YouTube.
教育背景: Bauman Moscow State Technical University. Information Systems and Technologies
专业领域:
Technical Documentation DevOps System Administration Linux Docker and Kubernetes CI/CD Infrastructure Automation Cloud Technologies System Monitoring Bash and Python Scripting

分享文章: