mcpField
C
cloudflare-turnstile

cloudflare-turnstile

github.com/secondsky安全240245 次安装5 周前更新
代码仓库

关于 cloudflare-turnstile

cloudflare-turnstile 是为在网站中集成 Cloudflare Turnstile 的开发者准备的实现与排错技能,涵盖客户端组件和服务器端令牌验证。内容还包括 React、测试及常见错误处理。

主要功能

  1. 01Turnstile 组件配置指导
  2. 02客户端渲染与令牌处理
  3. 03服务器端 Siteverify 验证
  4. 04React 与 Next.js 集成模式
  5. 05端到端测试指导
  6. 06错误代码与故障排查

适用场景

  1. 01为表单添加机器人防护验证
  2. 02在 Workers API 路由验证令牌
  3. 03将 Turnstile 接入 React 表单
  4. 04使用测试密钥配置端到端测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/secondsky/claude-skills --skill cloudflare-turnstile

安装后在 Claude Code 中输入/cloudflare-turnstile确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-topics.md
browser-support.md
common-patterns.md
error-codes.md
migration-guide.md
mobile-implementation.md
react-integration.md
setup-checklist.md
testing-guide.md
widget-configs.md
scripts/
check-csp.sh
templates/
turnstile-hono-route.ts
turnstile-react-component.tsx
turnstile-server-validation.ts
turnstile-test-config.ts
turnstile-widget-explicit.ts
turnstile-widget-implicit.html
wrangler-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:创建小组件配置
  1. 登录 Cloudflare Dashboard
  2. 前往 Turnstile 部分
  3. 点击“Add Site”
  4. 配置:
    • 小组件模式: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

解决方案:

  1. 检查令牌是否已过期(TTL 为 5 分钟)
  2. 验证密钥是否正确
  3. 确保令牌尚未验证过(只能使用一次)
  4. 检查主机名是否与小组件配置匹配
问题: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 项验证

评论

支持 Markdown