关于 cloudflare-turnstile
cloudflare-turnstile 是为在网站中集成 Cloudflare Turnstile 的开发者准备的实现与排错技能,涵盖客户端组件和服务器端令牌验证。内容还包括 React、测试及常见错误处理。
主要功能
- 01Turnstile 组件配置指导
- 02客户端渲染与令牌处理
- 03服务器端 Siteverify 验证
- 04React 与 Next.js 集成模式
- 05端到端测试指导
- 06错误代码与故障排查
适用场景
- 01为表单添加机器人防护验证
- 02在 Workers API 路由验证令牌
- 03将 Turnstile 接入 React 表单
- 04使用测试密钥配置端到端测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill cloudflare-turnstile安装后在 Claude Code 中输入/cloudflare-turnstile确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-topics.mdbrowser-support.mdcommon-patterns.mderror-codes.mdmigration-guide.mdmobile-implementation.mdreact-integration.mdsetup-checklist.mdtesting-guide.mdwidget-configs.mdscripts/check-csp.shtemplates/turnstile-hono-route.tsturnstile-react-component.tsxturnstile-server-validation.tsturnstile-test-config.tsturnstile-widget-explicit.tsturnstile-widget-implicit.htmlwrangler-turnstile-config.jsonc详细介绍
Cloudflare Turnstile
状态:已可用于生产环境 ✅ | 上次验证时间:2025-11-26
依赖项:无(可选:@marsidev/react-turnstile,适用于 React)
目录:快速开始 • 关键规则 • 12 个常见错误 • 常见模式 • 何时加载参考资料 • 故障排除
快速开始(10 分钟)
1. 创建 Turnstile 小组件
从 Cloudflare Dashboard 获取 sitekey 和 secret key。
# Navigate to: https://dash.cloudflare.com/?to=/:account/turnstile
# Create new widget → Copy sitekey (public) and secret key (private)
重要原因:
- 每个小组件都有唯一的 sitekey/secret 密钥对
- sitekey 放在前端(公开)
- secret key 仅放在后端(私密)
- 为开发、预发布和生产环境使用不同的小组件
2. 将小组件添加到前端
在 HTML 表单中嵌入 Turnstile 小组件。
<!DOCTYPE html>
<html>
<head>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form id="myForm" action="/submit" method="POST">
<input type="email" name="email" required>
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Submit</button>
</form>
</body>
</html>
关键事项:
- 切勿代理或缓存
api.js——必须从 Cloudflare CDN 加载 - 小组件会自动创建隐藏输入框
cf-turnstile-response,其中包含令牌 - 令牌会在 5 分钟后过期
- 每个令牌只能使用一次
3. 在服务器上验证令牌
始终在服务器端验证令牌。仅进行客户端验证并不安全。
// Cloudflare Workers example
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const formData = await request.formData()
const token = formData.get('cf-turnstile-response')
const ip = request.headers.get('CF-Connecting-IP')
// Validate token with Siteverify API
const verifyFormData = new FormData()
verifyFormData.append('secret', env.TURNSTILE_SECRET_KEY)
verifyFormData.append('response', token)
verifyFormData.append('remoteip', ip)
const result = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
body: verifyFormData,
}
)
const outcome = await result.json()
if (!outcome.success) {
return new Response('Invalid Turnstile token', { status: 401 })
}
// Token valid - proceed with form processing
return new Response('Success!')
}
}
三步设置流程
步骤 1:创建小组件配置
- 登录 Cloudflare Dashboard
- 前往 Turnstile 部分
- 点击“Add Site”
- 配置:
- 小组件模式:Managed(推荐)、Non-Interactive 或 Invisible
- 域名:添加允许的主机名(例如,example.com;开发环境可添加 localhost)
- 名称:填写描述性名称(例如,“Production Login Form”)
要点:
- 为开发、预发布和生产环境使用不同的小组件
- 将域名限制为你控制的域名
- Managed 模式在安全性和用户体验之间提供了最佳平衡
- 本地测试时必须显式添加 localhost
步骤 2:客户端集成
选择隐式渲染或显式渲染:
隐式渲染(推荐用于静态表单):
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-callback="onSuccess"
data-error-callback="onError"></div>
<script>
function onSuccess(token) {
console.log('Turnstile success:', token)
}
function onError(error) {
console.error('Turnstile error:', error)
}
</script>
显式渲染(适用于 SPA/动态 UI):
// 1. Load script with explicit mode
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" defer></script>
// 2. Render programmatically
const widgetId = turnstile.render('#container', {
sitekey: 'YOUR_SITE_KEY',
callback: (token) => {
console.log('Token:', token)
},
'error-callback': (error) => {
console.error('Error:', error)
},
theme: 'auto',
execution: 'render', // or 'execute' for manual trigger
})
// Control lifecycle
turnstile.reset(widgetId) // Reset widget
turnstile.remove(widgetId) // Remove widget
turnstile.execute(widgetId) // Manually trigger challenge
const token = turnstile.getResponse(widgetId) // Get current token
React 集成(使用 @marsidev/react-turnstile):
import { Turnstile } from '@marsidev/react-turnstile'
export function MyForm() {
const [token, setToken] = useState<string>()
return (
<form>
<Turnstile
siteKey={TURNSTILE_SITE_KEY}
onSuccess={setToken}
onError={(error) => console.error(error)}
/>
<button disabled={!token}>Submit</button>
</form>
)
}
步骤 3:服务器端验证
必须:始终调用 Siteverify API 验证令牌。
interface TurnstileResponse {
success: boolean
challenge_ts?: string
hostname?: string
error-codes?: string[]
action?: string
cdata?: string
}
async function validateTurnstile(
token: string,
secretKey: string,
options?: {
remoteip?: string
idempotency_key?: string
expectedAction?: string
expectedHostname?: string
}
): Promise<TurnstileResponse> {
const formData = new FormData()
formData.append('secret', secretKey)
formData.append('response', token)
if (options?.remoteip) {
formData.append('remoteip', options.remoteip)
}
if (options?.idempotency_key) {
formData.append('idempotency_key', options.idempotency_key)
}
const response = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
body: formData,
}
)
const result = await response.json<TurnstileResponse>()
// Additional validation
if (result.success) {
if (options?.expectedAction && result.action !== options.expectedAction) {
return { success: false, 'error-codes': ['action-mismatch'] }
}
if (options?.expectedHostname && result.hostname !== options.expectedHostname) {
return { success: false, 'error-codes': ['hostname-mismatch'] }
}
}
return result
}
// Usage in Cloudflare Worker
const result = await validateTurnstile(
token,
env.TURNSTILE_SECRET_KEY,
{
remoteip: request.headers.get('CF-Connecting-IP'),
expectedHostname: 'example.com',
}
)
if (!result.success) {
return new Response('Turnstile validation failed', { status: 401 })
}
关键规则
务必做到
✅ 调用 Siteverify API——服务器端验证是强制要求
✅ 使用 HTTPS——切勿通过 HTTP 进行验证
✅ 保护密钥——切勿在前端代码中暴露密钥
✅ 处理令牌过期——令牌会在 5 分钟后过期
✅ 实现错误回调——妥善处理失败情况
✅ 使用测试用虚拟密钥——测试 sitekey:1x00000000000000000000AA
✅ 设置合理的超时时间——不要无限期等待验证结果
✅ 验证 action/hostname——在指定时检查附加字段
✅ 定期轮换密钥——通过 Dashboard 或 API 轮换 secret
✅ 监控分析数据——跟踪验证通过率和失败情况
✅ 在提交表单后验证令牌——在用户完成表单后验证令牌,而不是之前。过早验证会产生安全漏洞,攻击者可借此获取有效令牌并绕过保护
切勿这样做
❌ 跳过服务器验证——仅进行客户端验证会带来安全漏洞
❌ 代理 api.js 脚本——必须从 Cloudflare CDN 加载
❌ 重复使用令牌——每个令牌只能使用一次
❌ 使用 GET 请求——Siteverify 仅接受 POST
❌ 暴露 secret key——仅在后端环境中保存密钥
❌ 信任客户端验证——令牌可能被伪造
❌ 缓存 api.js——后续更新可能导致集成失效
❌ 在测试中使用生产密钥——改用虚拟密钥
❌ 忽略错误回调——始终处理失败情况
已知问题预防
此技能可预防已记录的 12 个问题:
问题 #1:缺少服务器端验证
错误:Turnstile Analytics 仪表板中没有任何令牌验证记录 来源:https://developers.cloudflare.com/turnstile/get-started/ 原因:开发者只实现了客户端小组件,跳过了 Siteverify 调用 预防措施:所有模板都包含必需的服务器端验证,并使用 Siteverify API
问题 #2:令牌过期(5 分钟)
错误:延迟提交有效令牌后返回 success: false
来源:https://developers.cloudflare.com/turnstile/get-started/server-side-validation
原因:令牌在生成 300 秒后过期
预防措施:模板会说明 TTL,并在令牌过期时实现令牌刷新
问题 #3:密钥暴露在前端
错误:安全机制被绕过——攻击者可以验证自己的令牌 来源:https://developers.cloudflare.com/turnstile/get-started/server-side-validation 原因:密钥被硬编码在 JavaScript 中或在源代码中可见 预防措施:所有模板都展示仅在后端进行验证,并使用环境变量
问题 #4:向 Siteverify 发送 GET 请求
错误:API 返回 405 Method Not Allowed 来源:https://developers.cloudflare.com/turnstile/migration/recaptcha 原因:reCAPTCHA 支持 GET,而 Turnstile 要求 POST 预防措施:模板使用 POST,并通过 FormData 或 JSON 请求体传递数据
问题 #5:内容安全策略拦截
错误:错误 200500——“加载错误:无法加载 iframe” 来源:https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes 原因:CSP 拦截了 challenges.cloudflare.com iframe 预防措施:Skill 包含 CSP 配置参考和 check-csp.sh 脚本
问题 #6:小组件崩溃(错误 300030)
错误:合法用户遇到通用客户端执行错误 来源:https://community.cloudflare.com/t/turnstile-is-frequently-generating-300x-errors/700903 原因:未知——似乎是 Cloudflare 方面的问题(2025 年) 预防措施:模板实现错误回调、重试逻辑和回退处理
问题 #7:配置错误(错误 600010)
错误:小组件因“配置错误”而失败 来源:https://community.cloudflare.com/t/repeated-cloudflare-turnstile-error-600010/644578 原因:小组件配置中缺少或已删除主机名 预防措施:模板说明主机名允许列表要求和验证步骤
问题 #8:Safari 18 / macOS 15“隐藏 IP”问题
错误:启用 Safari 的“隐藏 IP 地址”后出现错误 300010 来源:https://community.cloudflare.com/t/turnstile-is-frequently-generating-300x-errors/700903 原因:隐私设置干扰了挑战信号 预防措施:错误处理参考文档说明了 Safari 的解决方法(关闭“隐藏 IP”)
问题 #9:Brave 浏览器彩纸动画故障
错误:成功动画期间验证失败 来源:https://github.com/brave/brave-browser/issues/45608 (2025 年 4 月) 原因:Brave 防护功能拦截了动画脚本 预防措施:模板会在动画完成前处理成功状态
问题 #10:Next.js + Jest 不兼容
错误:@marsidev/react-turnstile 导致 Jest 测试失败 来源:https://github.com/marsidev/react-turnstile/issues/112 (2025 年 10 月) 原因:Jest 的模块解析问题 预防措施:测试指南包含 Jest 模拟模式和虚拟 sitekey 的用法
问题 #11:localhost 不在允许列表中
错误:错误 110200——“未知域名:不允许此域名” 来源:https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes 原因:开发环境使用了生产小组件,但允许列表中未包含 localhost 预防措施:模板在开发环境中使用虚拟测试密钥,并说明 localhost 允许列表要求
问题 #12:尝试重复使用令牌
错误:success: false,并出现 "token already spent" 错误
来源:https://developers.cloudflare.com/turnstile/troubleshooting/testing
发生原因:每个令牌只能验证一次
预防措施:模板说明了令牌只能使用一次的限制以及令牌刷新模式
配置
Wrangler(Workers):加载 templates/wrangler-turnstile-config.jsonc 以获取完整配置。关键设置:vars 用于存放公开的 sitekey(可以安全提交),secrets 用于存放私有密钥(使用 wrangler secret put TURNSTILE_SECRET_KEY)。
CSP 指令(如果使用内容安全策略):
<meta http-equiv="Content-Security-Policy" content="
script-src 'self' https://challenges.cloudflare.com;
frame-src 'self' https://challenges.cloudflare.com;
connect-src 'self' https://challenges.cloudflare.com;">
常见模式
Hono + Cloudflare Workers:在采用 Hono 框架的 Workers API 路由中进行服务端验证。构建需要机器人防护的 Workers 端点时,加载 references/common-patterns.md #pattern-1。
React + Next.js:使用 @marsidev/react-turnstile 集成客户端表单。将 Turnstile 集成到 React/Next.js 应用时,加载 references/common-patterns.md #pattern-2。
端到端测试:使用虚拟密钥进行自动化测试(Playwright、Cypress、Jest)。编写端到端测试或设置 CI/CD 流水线时,加载 references/common-patterns.md #pattern-3。
组件生命周期:为单页应用(SPA)提供程序化组件控制(渲染、重置、移除、获取令牌)。构建需要显式管理组件的 SPA 时,加载 references/common-patterns.md #pattern-4。
何时加载参考资料
references/widget-configs.md:配置组件外观、主题、执行模式、大小、语言或重试行为。
references/error-codes.md:调试 100*、200*、300*、400*、600* 错误代码,或排查客户端故障(CSP、域名错误、组件崩溃)。
references/testing-guide.md:设置端到端测试(Playwright、Cypress)、使用虚拟密钥进行本地开发,或集成 CI/CD 流水线。
references/react-integration.md:集成 React、Next.js,或排查 @marsidev/react-turnstile 问题(Jest 模拟、SSR、钩子)。
references/common-patterns.md:构建 Hono Workers 路由、React 表单、端到端测试,或管理组件生命周期(显式渲染)。
references/advanced-topics.md:为 SPA 实现预清除、设置自定义 actions/cdata、重试策略或多组件页面。
references/setup-checklist.md:准备部署、验证完整设置,或确保已做好生产环境准备(14 项检查清单)。
references/migration-guide.md:从 reCAPTCHA(v2)或 hCaptcha 迁移到 Turnstile,包括兼容模式、API 差异以及 Siteverify 仅支持 POST 的要求。
references/browser-support.md:浏览器兼容性矩阵、Safari 18“隐藏 IP”解决方法、Brave Shields 问题以及针对特定浏览器的回退方案。
references/mobile-implementation.md:iOS、Android、React Native 和 Flutter 的 WebView 集成,包括 User Agent 一致性和存储持久化要求。
templates/:wrangler-turnstile-config.jsonc(Workers 环境)、turnstile-widget-implicit.html(静态表单)、turnstile-widget-explicit.ts(SPA 渲染)、turnstile-server-validation.ts(Siteverify API)、turnstile-react-component.tsx(React 集成)、turnstile-hono-route.ts(Hono 验证)、turnstile-test-config.ts(测试设置)
scripts/check-csp.sh:验证内容安全策略是否允许 Turnstile(用法:./scripts/check-csp.sh https://example.com)
依赖项
必需:无(Turnstile 从 Cloudflare CDN 加载)
可选:@marsidev/[email protected](React)、[email protected](TypeScript)、vue-turnstile(Vue 3)、ngx-turnstile(Angular)、svelte-turnstile(Svelte)、@nuxtjs/turnstile(Nuxt)
官方文档
Turnstile:https://developers.cloudflare.com/turnstile/ • 快速入门:https://developers.cloudflare.com/turnstile/get-started/ • 错误代码:https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes/ • 测试:https://developers.cloudflare.com/turnstile/troubleshooting/testing/ • 迁移(reCAPTCHA):https://developers.cloudflare.com/turnstile/migration/recaptcha/ • MCP:使用 mcp__cloudflare-docs__search_cloudflare_documentation 工具
故障排除
问题:错误 110200 - “未知域名”
解决方案:在 Cloudflare 控制面板中将你的域名(包括用于开发的 localhost)添加到小组件的允许域名列表中。本地开发时,请改用虚拟测试 sitekey 1x00000000000000000000AA。
问题:错误 300030 - 合法用户的小组件崩溃
解决方案:实现带有重试逻辑的错误回调。这是 Cloudflare 端的已知问题(2025 年)。如果重试失败,请回退到其他验证方式。
问题:令牌始终返回 success: false
解决方案:
- 检查令牌是否已过期(TTL 为 5 分钟)
- 验证密钥是否正确
- 确保令牌尚未验证过(只能使用一次)
- 检查主机名是否与小组件配置匹配
问题:CSP 阻止 iframe(错误 200500)
解决方案:添加 CSP 指令:
<meta http-equiv="Content-Security-Policy" content="
frame-src https://challenges.cloudflare.com;
script-src https://challenges.cloudflare.com;
">
问题:Safari 18 的“隐藏 IP”导致错误 300010
解决方案:在错误消息中说明,用户应关闭 Safari 的“隐藏 IP 地址”设置(Safari → 设置 → 隐私 → 隐藏 IP 地址 → 关闭)
问题:Next.js + Jest 测试使用 @marsidev/react-turnstile 时失败
解决方案:在 Jest 设置中模拟 Turnstile 组件:
// jest.setup.ts
jest.mock('@marsidev/react-turnstile', () => ({
Turnstile: () => <div data-testid="turnstile-mock" />,
}))
令牌效率:相比手动集成,节省约 65-70%
避免的错误:提供完整解决方案,涵盖 12 个已记录的安全/验证问题
部署检查清单:加载 references/setup-checklist.md,完成部署前的 14 项验证