Cloudflare Turnstile 配置完全指南:脚本加载、组件选项与框架集成的实战手册(Codex Skills 仓库深度解析)
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
本篇技术指南以本仓库 Codex Skills 目录中 cloudflare-deploy 技能下的 Turnstile 配置参考文档 为核心骨架,系统讲解 Cloudflare Turnstile(一种无需用户手动拼图/勾选的人机验证方案)的前端配置全流程:从四种脚本加载方式、完整的组件 Options 对象、HTML 数据属性映射,到 Content Security Policy 与 React / Vue / Svelte / Next.js 框架接入,以及 Cloudflare Pages 官方插件。读完本文,你将能够独立完成 Turnstile 从「引入脚本 → 渲染组件 → 服务端校验 → 框架集成」的完整落地,并规避 token 过期、单次使用、CSP 拦截等常见坑点。
一、Turnstile 是什么:先理解它在整个技能体系中的定位
在本仓库中,cloudflare-deploy 是一个「部署应用到 Cloudflare」的整合型技能,其 SKILL.md 通过决策树引导 Agent 选择合适产品:在 "I need security"(我需要安全能力)分支下,CAPTCHA alternative → turnstile/。也就是说,Turnstile 在本技能中承担的是人机验证/反机器人这一安全职责,与 WAF、DDoS、Bot Management、API Shield 并列。
依据同目录 turnstile README,Turnstile 是一种用户友好的 CAPTCHA 替代品:它在后台运行挑战,无需用户交互即可完成验证,通过浏览器行为、设备指纹与机器学习等信号自动判断访问者是否为真人。配置文档(即本文核心)则负责回答「怎么把验证组件渲染到页面上、怎么配置它的行为」。
在动手配置之前,需要先了解 Turnstile 的三种组件形态(源自 README.md),因为它们直接决定后续配置方式:
| 类型 | 交互方式 | 适用场景 |
|---|---|---|
| Managed(托管,默认) | 仅在需要时显示复选框 | 表单、登录页——兼顾体验与安全 |
| Non-Interactive(非交互) | 不可见,自动运行 | 无感体验、低风险操作 |
| Invisible(隐形) | 隐藏,通过代码触发 | 预放行(Pre-clearance)、API 调用、无头环境 |
同时必须记住三条硬性约束(源自 README 与 gotchas.md):
- Token 有效期 5 分钟:超过 5 分钟即失效;
- Token 单次使用:每个 token 只能被 siteverify 校验一次,重复校验会报
timeout-or-duplicate; - 必须服务端校验:仅靠前端校验可被轻易绕过。
二、脚本加载:四种方式各取所需
配置的起点是引入官方脚本,脚本地址统一为https://challenges.cloudflare.com/turnstile/v0/api.js。configuration.md 给出四种加载方式,对应不同的渲染控制粒度。
1. 基础方式(隐式渲染,Implicit Rendering)
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>async defer保证脚本异步加载且不阻塞页面解析。脚本加载后会自动扫描页面中带有class="cf-turnstile"的元素,在页面加载时自动渲染为 Turnstile 组件——这就是「隐式渲染」:无需写任何 JavaScript,只需在表单里放一个<div class="cf-turnstile"><script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
通过 URL 参数render=explicit关闭自动渲染,改为通过window.turnstile.render()手动控制组件渲染的时机与位置。适用于 SPA、动态插入容器、或需要精确控制渲染时机的场景。
3. 带加载回调(With Load Callback)
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=myCallback"></script> <script> function myCallback() { // API ready window.turnstile.render('#container', { sitekey: 'YOUR_SITE_KEY' }); } </script>onload参数指定一个全局回调函数名,脚本加载完成、window.turnstileAPI 就绪后立即调用。注意:api.md 中的 TypeScript 声明还支持window.onloadTurnstileCallback这种命名约定,两种写法等价,选择其一保持一致即可。
4. 兼容模式(Compatibility Mode)
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha"></script>加上compat=recaptcha后,脚本会额外提供grecaptchaAPI,让已接入 Google reCAPTCHA 的老代码可以零改造成本地切换到 Turnstile(drop-in replacement)。适合存量项目迁移。
三、组件配置:完整的 Options 对象与关键选项语义
显式渲染(window.turnstile.render(container, options))的核心是 Options 对象。configuration.md 给出了完整的配置结构,下面按「必填 / 回调 / 外观 / 行为 / 表单集成 / 分析与数据」六个维度完整呈现,并补充默认值与取值说明:
{ // —— 必填 —— sitekey: 'YOUR_SITE_KEY', // 从 Cloudflare 控制台创建 Widget 后获得的站点密钥 // —— 回调函数 —— callback: (token) => {}, // 校验成功,token 已就绪(在表单提交前一定要等到它) 'error-callback': (code) => {}, // 发生错误,code 为错误码 'expired-callback': () => {}, // token 过期(>5 分钟) 'timeout-callback': () => {}, // 挑战超时 'before-interactive-callback': () => {}, // 展示复选框之前触发 'after-interactive-callback': () => {}, // 用户交互完成后触发 'unsupported-callback': () => {}, // 浏览器不支持 Turnstile 时触发 // —— 外观 —— theme: 'auto', // 'light' | 'dark' | 'auto'(跟随系统) size: 'normal', // 'normal' | 'compact' | 'flexible' tabindex: 0, // 键盘 Tab 顺序(可访问性) language: 'auto', // ISO 639-1 语言码(如 'en')或 'auto' // —— 行为 —— execution: 'render', // 'render'(渲染即开始挑战)| 'execute'(等 turnstile.execute() 手动触发) appearance: 'always', // 'always' | 'execute' | 'interaction-only' retry: 'auto', // 'auto'(失败自动重试)| 'never' 'retry-interval': 8000, // 重试间隔(毫秒),默认 8000 'refresh-expired': 'auto', // 'auto' | 'manual' | 'never' // —— 表单集成 —— 'response-field': true, // 是否自动生成隐藏 input 存放 token(默认 true) 'response-field-name': 'cf-turnstile-response', // 隐藏 input 的 name,服务端据此取值 // —— 分析与数据 —— action: 'login', // 动作名称,用于分析(siteverify 响应中原样返回) cData: 'user-session-123', // 自定义数据,siteverify 校验时原样返回 }关键选项深度解读
execution(挑战触发时机)
'render'(默认):组件渲染后挑战立即开始,用户无需等待;'execute':组件渲染后不自动发起挑战,必须调用window.turnstile.execute()才触发。适合把挑战推迟到用户点击提交按钮之后,配合appearance: 'execute'可实现「先隐藏、提交时才出验证」。
appearance(可见性策略)
'always'(默认):组件始终可见;'execute':隐藏,直到调用execute();'interaction-only':隐藏,直到真正需要用户交互时才展示(Managed 类型常用)。
refresh-expired(过期 token 处理)
'auto'(默认):token 过期后自动刷新;'manual':过期后应用需自行调用turnstile.reset()重置;'never':不刷新,只触发expired-callback。
retry(挑战失败重试)
'auto'(默认):挑战失败自动重试;'never':不重试,直接触发error-callback。
这些选项的取值域与 api.md 中的
TurnstileOptionsTypeScript 接口完全一致(execution?: 'render' | 'execute'、appearance?: 'always' | 'execute' | 'interaction-only'等),TypeScript 项目可直接获得编译期类型检查。
四、HTML 数据属性:隐式渲染的声明式配置
隐式渲染(<div class="cf-turnstile">)不需要写 JavaScript,所有配置通过 data 属性声明。configuration.md 给出了完整的「JavaScript 属性 ↔ HTML 数据属性」映射表,以下为全量对照:
| JavaScript 属性 | HTML 数据属性 | 示例 |
|---|---|---|
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" |
注意数据属性中的回调(如data-callback="onSuccess")指向的是全局函数名,因此隐式渲染要求相关回调函数定义在全局作用域。
完整示例:
<div class="cf-turnstile" ><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://challenges.cloudflare.com; frame-src https://challenges.cloudflare.com;">gotchas.md 明确把CSP 拦截列为「Widget 不渲染」的头号原因之一,其余常见原因还包括:sitekey 错误、file://协议下无法加载(应改用http://本地服务)。若组件不渲染,请优先按此排查。
六、框架级接入:React / Vue / Svelte / Next.js
React
官方推荐的社区封装包为@marsidev/react-turnstile:
npm install @marsidev/react-turnstileimport Turnstile from '@marsidev/react-turnstile'; <Turnstile siteKey="YOUR_SITE_KEY" onSuccess={(token) => console.log(token)} />Vue
npm install vue-turnstile<template> <VueTurnstile site-key="YOUR_SITE_KEY" @success="onSuccess" /> </template> <script setup> import VueTurnstile from 'vue-turnstile'; </script>Svelte
npm install svelte-turnstile<script> import Turnstile from 'svelte-turnstile'; </script> <Turnstile siteKey="YOUR_SITE_KEY" on:turnstile-callback={handleToken} />Next.js(App Router)
由于window.turnstile只在浏览器端存在,必须用'use client'声明客户端组件,并在useEffect中完成渲染与清理(源自 configuration.md):
// app/components/TurnstileWidget.tsx 'use client'; import { useEffect, useRef } from 'react'; export default function TurnstileWidget({ sitekey, onSuccess }) { const ref = useRef<HTMLDivElement>(null); useEffect(() => { if (ref.current && window.turnstile) { const widgetId = window.turnstile.render(ref.current, { sitekey, callback: onSuccess }); return () => window.turnstile.remove(widgetId); } }, [sitekey, onSuccess]); return <div ref={ref} />; }框架接入的三大坑(源自 gotchas.md)
- React 组件重挂载丢 token:组件因 state 变化重新渲染时,验证 token 会丢失。解决思路是用
useRef锁定渲染生命周期,仅在首次挂载时render、卸载时remove; - React StrictMode 双重渲染:开发模式下 StrictMode 会执行两次 effect,必须提供清理函数(
return () => window.turnstile.remove(widgetId))避免出现两个叠加的组件实例; - Next.js SSR 水合问题:
window.turnstile在服务端渲染阶段为undefined,组件必须'use client',或使用dynamic(..., { ssr: false })动态导入。
七、Cloudflare Pages 插件:一行代码接入服务端校验
如果站点部署在 Cloudflare Pages,可以直接使用官方中间件插件,把验证逻辑放进 Functions 中间件,无需手写 siteverify 调用(源自 configuration.md):
npm install @cloudflare/pages-plugin-turnstile// functions/_middleware.ts import turnstilePlugin from '@cloudflare/pages-plugin-turnstile'; export const onRequest = turnstilePlugin({ secret: 'YOUR_SECRET_KEY', onError: () => new Response('CAPTCHA failed', { status: 403 }) });只要请求通过_middleware.ts,插件就会自动校验请求携带的 Turnstile token,校验失败直接返回 403。注意:secret是服务端密钥,绝不能出现在客户端代码中(详见下文安全底线)。
八、配合服务端校验:让配置真正闭环
配置文档负责「前端渲染」,但人机验证的安全闭环必须落在服务端。结合 api.md 与 patterns.md,siteverify 的完整流程如下:
端点:POST https://challenges.cloudflare.com/turnstile/v0/siteverify
请求体字段:
| 字段 | 类型 | 说明 |
|---|---|---|
secret | string | 你的密钥(绝不暴露给客户端) |
response | string | 前端提交的 token(来自cf-turnstile-response隐藏字段) |
remoteip | string | 用户 IP(可选但建议传) |
idempotency_key | string | 幂等校验唯一键(可选) |
响应字段:success(校验结果)、challenge_ts(挑战时间戳 ISO)、hostname(解决验证的域名)、error-codes(失败时的错误码数组)、action(组件配置的动作名)、cdata(组件配置的自定义数据)。
Cloudflare Workers 服务端校验示例(源自 README 与 patterns.md):
export default { async fetch(request) { const formData = await request.formData(); const token = formData.get('cf-turnstile-response'); 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 validation = await result.json(); if (!validation.success) { return new Response('Invalid CAPTCHA', { status: 400 }); } // Process form... } }常见错误码速查(源自 api.md):
| 错误码 | 原因 | 处理 |
|---|---|---|
missing-input-secret | 未提供 secret | 请求中带上secret |
invalid-input-secret | secret 错误 | 在控制台核对密钥 |
missing-input-response | 未提供 token | 带上responsetoken |
invalid-input-response | token 非法/格式错误 | 确认来自组件的 token |
timeout-or-duplicate | token 过期(>5 分钟)或重复使用 | 生成新 token,只能校验一次 |
internal-error | Cloudflare 服务端错误 | 指数退避重试 |
bad-request | 请求格式错误 | 检查 JSON/表单编码 |
四条不可妥协的安全底线(源自 gotchas.md):
- 禁止只做前端校验:客户端校验可被轻易绕过,必须服务端调用 siteverify;
- 禁止暴露 secret:密钥只存在于服务端环境变量,绝不写入前端代码;
- 禁止复用 token:token 单次有效,提交失败后调用
window.turnstile.reset(widgetId)生成新 token; - 必须处理过期:配置
refresh-expired: 'auto',或在expired-callback中重置组件。
环境区分测试密钥(源自 README 与 patterns.md,测试密钥在 localhost 与任意域名均可用,严禁用于生产):
| 类型 | 密钥 | 行为 |
|---|---|---|
| Site Key(始终通过) | 1x00000000000000000000AA | 组件成功,token 可通过校验 |
| Site Key(始终拦截) | 2x00000000000000000000AB | 组件可见地失败 |
| Site Key(强制挑战) | 3x00000000000000000000FF | 总是展示交互挑战 |
| Secret Key(测试) | 1x0000000000000000000000000000000AA | 校验测试 token |
生产/测试切换建议按环境变量区分:
const SITE_KEY = process.env.NODE_ENV === 'production' ? process.env.TURNSTILE_SITE_KEY : '1x00000000000000000000AA';九、调试三板斧与约束速查
调试建议(源自 gotchas.md):
- 组件渲染时同时注册
callback/error-callback/expired-callback/timeout-callback并输出日志,第一时间定位状态; - 用
window.turnstile.getResponse(widgetId)检查 token 是否就绪,用window.turnstile.isExpired(widgetId)检查是否过期; - 打开浏览器 Network 面板:确认
api.js返回 200、观察 siteverify 请求/响应、排查 4xx/5xx。
约束速查表:
| 约束 | 值 | 影响 |
|---|---|---|
| Token 有效期 | 5 分钟 | 过期需重新生成 |
| Token 使用次数 | 单次 | 不能重复校验同一 token |
| 组件尺寸 | normal 300×65px;compact 130×120px | 布局时预留空间 |
十、总结
Turnstile 的配置本质上是一条「加载脚本 → 渲染组件(隐式/显式)→ 收集 token → 服务端 siteverify 校验」的闭环链路:configuration.md 解决了链路的前半段(脚本加载、Options 配置、数据属性、CSP、框架接入、Pages 插件),而后半段的校验细节可继续阅读同目录的 api.md(客户端 API 与 siteverify 接口)、patterns.md(表单集成与预放行模式)、gotchas.md(排错与反模式)。把「5 分钟过期、单次使用、必须服务端校验、密钥不出服务端」这四条底线刻在脑海里,你的 Turnstile 接入就能既顺滑又安全。
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考