Cloudflare Turnstile 完整 API 参考:前端 JavaScript API 与 Siteverify 服务端校验实战
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
本文以 skills/.curated/cloudflare-deploy/references/turnstile/api.md 为骨架,系统讲解 Cloudflare Turnstile 的客户端 JavaScript API(
window.turnstile)、Siteverify 服务端校验接口、错误码与 TypeScript 类型定义,并结合同目录下的 configuration.md、patterns.md 与 gotchas.md 做纵深补充。读完本文,你将能够在 Cloudflare Workers / Pages Functions 或任意后端环境中完整实现「前端渲染 → 取 token → 服务端校验」的闭环,并掌握 Token 过期、单次使用、CSP 与密钥安全等关键约束。
该文档隶属于本仓库 cloudflare-deploy 技能下的安全产品参考集(Security → Turnstile),是面向 Agent 与开发者的 Turnstile 集成权威参考,属于「CAPTCHA 替代方案」决策分支的落地细节层。
一、Turnstile 是什么:无感验证与两种核心 API
Turnstile 是 Cloudflare 提供的智能 CAPTCHA 替代方案:它在后台基于浏览器行为、设备指纹与机器学习信号自动完成访客验证,用户几乎无感知,不出现传统拼图式验证码。其集成模型由两部分 API 组成:
- 客户端 JavaScript API:脚本加载后暴露在
window.turnstile上,负责在页面中渲染 widget、生成 token、重置或移除 widget; - Siteverify API(服务端):把客户端生成的 token 连同 secret 发送到
https://challenges.cloudflare.com/turnstile/v0/siteverify进行最终校验,这一步是安全闭环的必选项——纯客户端校验可被轻易绕过。
阅读顺序(见 README.md)建议为:configuration(配置)→ api(本文)→ patterns(模式)→ gotchas(排错)。
二、脚本加载方式:从加载到可用的三种姿势
Turnstile 的所有客户端能力都来自api.js,加载方式直接影响渲染时机与兼容性(本文档「Script Loading」一节给出的三种方式):
<!-- 1. 标准加载:页面加载后自动渲染所有 class="cf-turnstile" 的容器(隐式渲染) --> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> <!-- 2. 显式渲染模式:只有调用 window.turnstile.render() 才渲染 --> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script> <!-- 3. 带加载回调:api.js 就绪后触发全局回调 --> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback"></script> <script> window.onloadTurnstileCallback = () => { window.turnstile.render('#container', { sitekey: 'YOUR_SITE_KEY' }); }; </script>此外 configuration.md 还补充了两种加载变体:
<!-- 兼容模式:暴露 grecaptcha API,可作 Google reCAPTCHA 的 drop-in 替代 --> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha"></script>- 隐式渲染(Implicit):不加
?render=参数,页面加载后自动扫描class="cf-turnstile"元素并按 HTML data 属性渲染,适合快速接入; - 显式渲染(Explicit):加
?render=explicit,完全由window.turnstile.render()手动控制渲染时机与位置,适合 SPA、条件渲染、受控表单。
三、客户端 JavaScript API 全解:window.turnstile
脚本加载完成后,Turnstile JavaScript API 通过全局对象window.turnstile提供五个核心方法。所有方法都以render()返回的widget ID为操作句柄(部分方法也接受容器元素)。
3.1turnstile.render(container, options)—— 渲染 widget
- 参数:
container为 CSS 选择器字符串或 DOM 元素;options为TurnstileOptions配置对象(详见本文第六节,完整字段见 configuration.md); - 返回:
string类型的 widget ID,供其它 API 方法使用。
const widgetId = window.turnstile.render('#my-container', { sitekey: 'YOUR_SITE_KEY', callback: (token) => console.log('Success:', token), 'error-callback': (code) => console.error('Error:', code) });3.2turnstile.reset(widgetId)—— 重置 widget
清除已生成的 token、重置挑战状态。典型场景是表单校验失败后强制用户重新验证(因为 token 单次有效且会过期)。
// 表单校验失败时重置 if (!validateForm()) { window.turnstile.reset(widgetId); }3.3turnstile.remove(widgetId)—— 彻底移除 widget
将 widget 从 DOM 中完全删除。典型场景是 SPA 路由切换、组件卸载时的清理(避免孤儿 widget)。
// 导航清理 window.turnstile.remove(widgetId);3.4turnstile.getResponse(widgetId)—— 获取当前 token
返回 widget 当前的有效 token(挑战已完成时),否则返回undefined。提交前用它判断是否具备可提交的验证凭据。
const token = window.turnstile.getResponse(widgetId); if (token) { submitForm(token); }3.5turnstile.isExpired(widgetId)—— 判断 token 是否过期
检查 token 是否已超过 5 分钟有效期,返回布尔值。适合在提交前自检,过期即reset()重新生成。
if (window.turnstile.isExpired(widgetId)) { window.turnstile.reset(widgetId); }3.6 完整的 TypeScript 接口定义
api.md 给出了Turnstile接口的完整 TS 声明,可直接作为类型依据:
interface Turnstile { render(container: string | HTMLElement, options: TurnstileOptions): string; reset(widgetId: string): void; remove(widgetId: string): void; getResponse(widgetId: string): string | undefined; isExpired(widgetId: string): boolean; execute(container?: string | HTMLElement, options?: TurnstileOptions): void; } declare global { interface Window { turnstile: Turnstile; onloadTurnstileCallback?: () => void; } }其中execute()配合execution: 'execute'与appearance: 'execute'使用,用于「预清除(pre-clearance)」等延迟执行场景(详见 patterns.md)。
四、回调签名(Callback Signatures)
widget 生命周期中的关键节点都通过回调暴露,API 文档给出了七种标准签名:
type TurnstileCallback = (token: string) => void; // 挑战成功,token 就绪 type ErrorCallback = (errorCode: string) => void; // 发生错误(携带错误码) type TimeoutCallback = () => void; // 挑战超时 type ExpiredCallback = () => void; // token 过期(>5 分钟) type BeforeInteractiveCallback = () => void; // 即将展示可交互元素前 type AfterInteractiveCallback = () => void; // 用户交互完成后 type UnsupportedCallback = () => void; // 浏览器不支持 Turnstile对应到 options 中为:callback、'error-callback'、'timeout-callback'、'expired-callback'、'before-interactive-callback'、'after-interactive-callback'、'unsupported-callback'。调试期可在每个回调里打日志快速定位问题(见 gotchas.md 的 Console Logging 模式)。
五、Siteverify API(服务端校验)
客户端 token 只是「凭据」,真正的安全判定在服务端完成。
5.1 端点与请求
- Endpoint:
https://challenges.cloudflare.com/turnstile/v0/siteverify - Method:POST
- Content-Type:
application/json或application/x-www-form-urlencoded
请求体结构:
interface SiteverifyRequest { secret: string; // 你的 secret key,绝不能暴露在客户端 response: string; // 来自表单隐藏域 cf-turnstile-response 的 token remoteip?: string; // 用户 IP(可选但推荐) idempotency_key?: string; // 幂等校验唯一键(可选) }Cloudflare Workers 中的标准调用示例:
const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ secret: env.TURNSTILE_SECRET, response: token, remoteip: request.headers.get('CF-Connecting-IP') }) }); const data = await result.json();两个关键工程细节(来自 patterns.md 与 gotchas.md):
- remoteip 取值:Cloudflare Workers 用
CF-Connecting-IP;普通代理后端用X-Forwarded-For的首个 IP; - CORS 陷阱:Siteverify禁止在浏览器端调用(会触发 CORS 错误),正确路径是「前端 → 自己的后端 → Cloudflare Siteverify」。
5.2 响应结构
interface SiteverifyResponse { success: boolean; // 校验结果 challenge_ts?: string; // 挑战完成的 ISO 时间戳 hostname?: string; // 解决 widget 时所在的域名 'error-codes'?: string[]; // success=false 时的错误码 action?: string; // 来自 widget 配置的 action 名 cdata?: string; // 来自 widget 配置的自定义数据 }成功响应示例:
{ "success": true, "challenge_ts": "2024-01-15T10:30:00Z", "hostname": "example.com", "action": "login", "cdata": "user123" }失败响应示例:
{ "success": false, "error-codes": ["timeout-or-duplicate"] }5.3 完整的 Workers 校验闭环
将 patterns.md 中的完整示例与 api.md 的接口结合,一个可运行的服务端校验如下:
interface Env { TURNSTILE_SECRET: string; } export default { async fetch(request: Request, env: Env): Promise<Response> { if (request.method !== 'POST') { return new Response('Method not allowed', { status: 405 }); } const formData = await request.formData(); const token = formData.get('cf-turnstile-response'); if (!token) { return new Response('Missing token', { status: 400 }); } const ip = request.headers.get('CF-Connecting-IP'); const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ secret: env.TURNSTILE_SECRET, response: token, remoteip: ip }) }); const validation = await result.json(); if (!validation.success) { return new Response('CAPTCHA validation failed', { status: 403 }); } // 校验通过,继续处理表单… return new Response('Success'); } };Cloudflare Pages Functions 使用同一模式,通过ctx.env.TURNSTILE_SECRET与ctx.request取值(见 patterns.md)。此外还可直接用官方插件@cloudflare/pages-plugin-turnstile在functions/_middleware.ts中一行接入。
六、错误码速查表
Siteverify 返回的error-codes是排错的第一手线索:
| Code | Cause | Solution |
|---|---|---|
missing-input-secret | 请求未提供 secret | 在请求中包含secret字段 |
invalid-input-secret | secret 错误 | 到 Dashboard 核对 secret key |
missing-input-response | 未提供 token | 携带responsetoken |
invalid-input-response | token 无效或格式错误 | 确认为 widget 生成的合法 token |
timeout-or-duplicate | token 过期(>5 分钟)或被重复使用 | 重新生成 token,且只校验一次 |
internal-error | Cloudflare 服务端错误 | 指数退避后重试 |
bad-request | 请求格式错误 | 检查 JSON / 表单编码 |
其中timeout-or-duplicate是最常见的生产错误,根源在于两条硬性约束:token 5 分钟过期、token 单次有效(详见 gotchas.md)。不要把 token 缓存超过 5 分钟,也不要对同一 token 重复校验。
七、TurnstileOptions 完整类型与配置语义
api.md 给出的TurnstileOptions完整定义:
interface TurnstileOptions { sitekey: string; action?: string; cData?: string; callback?: (token: string) => void; 'error-callback'?: (errorCode: string) => void; 'expired-callback'?: () => void; 'timeout-callback'?: () => void; 'before-interactive-callback'?: () => void; 'after-interactive-callback'?: () => void; 'unsupported-callback'?: () => void; theme?: 'light' | 'dark' | 'auto'; size?: 'normal' | 'compact' | 'flexible'; tabindex?: number; 'response-field'?: boolean; 'response-field-name'?: string; retry?: 'auto' | 'never'; 'retry-interval'?: number; language?: string; execution?: 'render' | 'execute'; appearance?: 'always' | 'execute' | 'interaction-only'; 'refresh-expired'?: 'auto' | 'manual' | 'never'; }各关键字段的语义(configuration.md 详述):
sitekey(必填):Dashboard 分配的站点 key;action/cData:为业务打标,action标识场景(如login),cData携带自定义数据,两者都会原样出现在 Siteverify 响应中,可用于防重放与审计;execution:'render'(默认,渲染后立即开始挑战)或'execute'(等待手动调用turnstile.execute());appearance:'always'(默认始终可见)、'execute'(execute()前隐藏)、'interaction-only'(仅在需要用户交互时显示);refresh-expired:'auto'(默认自动刷新过期 token)、'manual'(过期后应用自行调用reset())、'never'(不刷新,仅触发expired-callback);retry/'retry-interval':'auto'(默认自动重试失败挑战,间隔默认 8000ms)或'never'(不重试,触发error-callback);'response-field'/'response-field-name':是否自动在表单内注入隐藏域,默认true,隐藏域 name 默认为cf-turnstile-response——服务端正是从这个字段名取 token 的,改名后服务端取值需同步修改;theme/size/language/tabindex:外观与无障碍配置,theme支持light/dark/auto,size支持normal/compact/flexible,language使用 ISO 639-1 码或auto。
隐式渲染:HTML data 属性映射
隐式渲染时,以上 JS 属性通过data-*属性映射(完整映射表见 configuration.md),这里列出常用部分:
| JavaScript Property | HTML Data Attribute | 示例 |
|---|---|---|
sitekey | data-sitekey | data-sitekey="YOUR_KEY" |
action | data-action | data-action="login" |
cData | data-cdata | data-cdata="session-123" |
callback | data-callback | data-callback="onSuccess" |
error-callback | data-error-callback | data-error-callback="onError" |
expired-callback | data-expired-callback | data-expired-callback="onExpired" |
timeout-callback | data-timeout-callback | data-timeout-callback="onTimeout" |
theme | data-theme | data-theme="dark" |
size | data-size | data-size="compact" |
tabindex | data-tabindex | data-tabindex="0" |
response-field | data-response-field | data-response-field="false" |
response-field-name | data-response-field-name | data-response-field-name="token" |
retry | data-retry | data-retry="never" |
retry-interval | data-retry-interval | data-retry-interval="5000" |
language | data-language | data-language="en" |
execution | data-execution | data-execution="execute" |
appearance | data-appearance | data-appearance="interaction-only" |
refresh-expired | data-refresh-expired | data-refresh-expired="manual" |
示例:
<div class="cf-turnstile" >const SITE_KEY = process.env.NODE_ENV === 'production' ? 'YOUR_PRODUCTION_SITE_KEY' : '1x00000000000000000000AA'; // Always passes const SECRET_KEY = process.env.NODE_ENV === 'production' ? process.env.TURNSTILE_SECRET : '1x0000000000000000000000000000000AA';8.3 CSP 配置
若站点启用了 Content Security Policy,必须放行 Turnstile 的脚本与 iframe 域:
script-src https://challenges.cloudflare.com; frame-src https://challenges.cloudflare.com;完整示例(configuration.md):
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://challenges.cloudflare.com; frame-src https://challenges.cloudflare.com;">未配置 CSP 是「widget 不渲染」的常见原因之一(gotchas.md)。
九、常见错误与调试清单
9.1 高频问题定位
| 错误现象 | 原因 | 解决 |
|---|---|---|
| Widget 不渲染 | sitekey 错误、CSP 拦截、file://协议 | 核对 sitekey、为 challenges.cloudflare.com 添加 CSP、改用 http:// |
timeout-or-duplicate | token 过期或复用 | 生成新 token,不缓存超过 5 分钟 |
invalid-input-secret | secret 错误 | 从 Dashboard 核对,检查环境变量 |
missing-input-response | token 未随请求发送 | 检查表单字段名是否为cf-turnstile-response |
9.2 客户端调试三件套
// 1. 全回调打日志 window.turnstile.render('#container', { sitekey: 'YOUR_SITE_KEY', callback: (token) => console.log('✓ Token:', token), 'error-callback': (code) => console.error('✗ Error:', code), 'expired-callback': () => console.warn('⏱ Expired'), 'timeout-callback': () => console.warn('⏱ Timeout') }); // 2. 检查 token 状态 const token = window.turnstile.getResponse(widgetId); console.log('Token:', token || 'NOT READY'); console.log('Expired:', window.turnstile.isExpired(widgetId));9.3 常见配置陷阱
- 密钥错配:sitekey 与 secret 必须来自同一个 widget,混用会直接校验失败;
- 测试密钥上生产:务必按 8.2 的环境变量方式隔离;
- 服务端 secret 未加载:检查
.env并验证!!process.env.TURNSTILE_SECRET; - SPA 组件重挂载:React 中因状态变化导致 widget 重渲染会丢失 token,需用
useRef控制生命周期并在卸载时remove()(React StrictMode 下要特别注意清理函数)。
9.4 排错顺序建议
- 先换用测试密钥
1x00000000000000000000AA+1x0000000000000000000000000000000AA排除 key 问题; - 打开 Network 面板确认
api.js返回 200、查看 siteverify 请求与响应体; - 检查是否有 4xx/5xx、CORS 或 CSP 拦截。
十、实战要点总结与延伸阅读
把本文的 API 知识点串成最小可用闭环:加载api.js→render()生成 widget 并拿到 widgetId → 表单提交时getResponse()取 token(或依赖自动注入的cf-turnstile-response隐藏域)→ 后端调用 Siteverify 校验 → 失败或过期则reset()重新挑战,卸载时remove()清理。
在此基础上,本仓库还提供了配套参考文档可继续深入:
- Turnstile 参考目录(Overview、Widget 类型、快速开始、测试密钥)
- Turnstile 配置指南(完整 options、data 属性映射、React/Vue/Svelte/Next.js 集成、Pages 插件)
- Turnstile 常用模式(表单集成、预清除、token 刷新、服务端校验完整代码)
- Turnstile 排错手册(常见错误、框架陷阱、限流约束、调试方法)
- cloudflare-deploy 技能总览(安全产品决策树,Turnstile 位于 Security 分支)
以上所有文档与示例代码均可直接在仓库内查看与复用,无需额外安装任何依赖。
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考