关于 cloudflare-turnstile
用于将 Cloudflare Turnstile 集成到网页表单和应用中的开发技能,涵盖组件配置、客户端渲染、服务端令牌验证、框架示例、测试与故障排查。
主要功能
- 01配置 Turnstile 组件
- 02在表单和 SPA 中渲染组件
- 03在服务端验证令牌
- 04采用 React 和 Cloudflare Workers 示例
- 05使用测试密钥
- 06排查错误和 CSP 问题
适用场景
- 01为网页表单添加机器人验证
- 02在 React 或 Next.js 应用中集成 Turnstile
- 03在后端验证提交的令牌
- 04在自动化测试中检查组件行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill cloudflare-turnstile安装后在 Claude Code 中输入/cloudflare-turnstile确认已加载。
附带文件README.mdSKILL.json
详细介绍
Cloudflare Turnstile
状态:生产就绪 最后更新:2025-10-22 依赖项:无(可选:React 可使用 @marsidev/react-turnstile) 最新版本:@marsidev/[email protected]、[email protected]
快速开始(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. 在前端添加小组件
将 Turnstile 小组件嵌入 HTML 表单。
<!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!')
}
}
3 步设置流程
第 1 步:创建小组件配置
- 登录 Cloudflare Dashboard
- 前往 Turnstile 页面
- 点击“Add Site”
- 配置:
- 小组件模式:Managed(推荐)、Non-Interactive 或 Invisible
- 域名:添加允许的主机名(例如,example.com;开发时可添加 localhost)
- 名称:填写描述性名称(例如,“生产环境登录表单”)
要点:
- 为开发、预发布和生产环境使用不同的小组件
- 仅将你控制的域名加入允许列表
- 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 验证
✅ 保护 secret key——切勿在前端代码中暴露
✅ 处理令牌过期——令牌会在 5 分钟后过期
✅ 实现错误回调——妥善处理失败情况
✅ 使用测试用密钥——测试 sitekey:1x00000000000000000000AA
✅ 设置合理的超时时间——不要无限期等待验证结果
✅ 验证 action/hostname——如有指定,请检查其他字段
✅ 定期轮换密钥——通过 Dashboard 或 API 轮换密钥
✅ 监控分析数据——跟踪通过率和失败情况
切勿这样做
❌ 跳过服务器验证——仅进行客户端验证会造成安全漏洞 ❌ 代理 api.js 脚本——必须从 Cloudflare CDN 加载 ❌ 重复使用令牌——每个令牌只能使用一次 ❌ 使用 GET 请求——Siteverify 只接受 POST ❌ 暴露 secret key——仅在后端环境中保存密钥 ❌ 信任客户端验证——令牌可能是伪造的 ❌ 缓存 api.js——未来的更新会导致集成失效 ❌ 在测试中使用生产密钥——请改用测试用密钥 ❌ 忽略错误回调——务必处理失败情况
预防已知问题
此技能可预防 12 个已记录的问题:
问题 #1:缺少服务器端验证
错误:Turnstile Analytics Dashboard 中没有任何令牌验证记录 来源: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 秒后过期
预防措施:模板会说明有效期,并在令牌过期时实现令牌刷新
问题 #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——“Loading error: The iframe could not be loaded” 来源:https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes 原因:CSP 阻止了 challenges.cloudflare.com iframe 预防措施:技能包含 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 (April 2025) 原因:Brave Shields 会阻止动画脚本 预防措施:模板会在动画完成前处理成功状态
问题 #10:Next.js 与 Jest 不兼容
错误:@marsidev/react-turnstile 会导致 Jest 测试失败 来源:https://github.com/marsidev/react-turnstile/issues/112 (Oct 2025) 原因:Jest 的模块解析问题 预防措施:测试指南包含 Jest 模拟模式和虚拟 sitekey 的用法
问题 #11:localhost 不在允许列表中
错误:错误 110200——“Unknown domain: Domain not allowed” 来源: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.jsonc(Cloudflare Workers)
{
"name": "my-app",
"main": "src/index.ts",
"compatibility_date": "2025-10-22",
// Public sitekey (safe to commit)
"vars": {
"TURNSTILE_SITE_KEY": "1x00000000000000000000AA" // Use real key in production
},
// Secret key (DO NOT commit - use wrangler secret)
// Run: wrangler secret put TURNSTILE_SECRET_KEY
"secrets": ["TURNSTILE_SECRET_KEY"]
}
这些设置的原因:
vars用于公开的 sitekey(在客户端代码中可见)secrets用于私有的 secret key(已加密,仅限后端使用)- 开发时使用虚拟密钥(参见 testing-guide.md)
- 每季度轮换生产环境的 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;
">
常见模式
模式 1:Hono + Cloudflare Workers
import { Hono } from 'hono'
type Bindings = {
TURNSTILE_SECRET_KEY: string
TURNSTILE_SITE_KEY: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.post('/api/login', async (c) => {
const body = await c.req.formData()
const token = body.get('cf-turnstile-response')
if (!token) {
return c.text('Missing Turnstile token', 400)
}
// Validate token
const verifyFormData = new FormData()
verifyFormData.append('secret', c.env.TURNSTILE_SECRET_KEY)
verifyFormData.append('response', token.toString())
verifyFormData.append('remoteip', c.req.header('CF-Connecting-IP') || '')
const verifyResult = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
body: verifyFormData,
}
)
const outcome = await verifyResult.json<{ success: boolean }>()
if (!outcome.success) {
return c.text('Invalid Turnstile token', 401)
}
// Process login
return c.json({ message: 'Login successful' })
})
export default app
适用场景:在 Cloudflare Workers 中使用 Hono 框架的 API 路由
模式 2:React + Next.js App Router
'use client'
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
export function ContactForm() {
const [token, setToken] = useState<string>()
const [error, setError] = useState<string>()
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
if (!token) {
setError('Please complete the challenge')
return
}
const formData = new FormData(e.currentTarget)
formData.append('cf-turnstile-response', token)
const response = await fetch('/api/contact', {
method: 'POST',
body: formData,
})
if (!response.ok) {
setError('Submission failed')
return
}
// Success
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
onSuccess={setToken}
onError={() => setError('Challenge failed')}
onExpire={() => setToken(undefined)}
/>
{error && <div className="error">{error}</div>}
<button type="submit" disabled={!token}>
Submit
</button>
</form>
)
}
适用场景:在 Next.js 中使用 React hooks 的客户端表单
模式 3:使用虚拟密钥进行 E2E 测试
// test/helpers/turnstile.ts
export const TEST_TURNSTILE = {
sitekey: {
alwaysPass: '1x00000000000000000000AA',
alwaysBlock: '2x00000000000000000000AB',
invisible: '1x00000000000000000000BB',
interactive: '3x00000000000000000000FF',
},
secretKey: {
alwaysPass: '1x0000000000000000000000000000000AA',
alwaysFail: '2x0000000000000000000000000000000AA',
tokenSpent: '3x0000000000000000000000000000000AA',
},
dummyToken: 'XXXX.DUMMY.TOKEN.XXXX',
}
// Playwright test example
test('form submission with Turnstile', async ({ page }) => {
// Set test environment
await page.goto('/contact?test=true')
// Widget uses test sitekey in test mode
await page.fill('input[name="email"]', '[email protected]')
// Turnstile auto-solves with dummy token
await page.click('button[type="submit"]')
await expect(page.locator('.success')).toBeVisible()
})
适用场景:自动化测试(Playwright、Cypress、Jest)
模式 4:小组件生命周期管理
class TurnstileManager {
private widgetId: string | null = null
private sitekey: string
constructor(sitekey: string) {
this.sitekey = sitekey
}
render(containerId: string, callbacks: {
onSuccess: (token: string) => void
onError: (error: string) => void
}) {
if (this.widgetId !== null) {
this.reset() // Reset if already rendered
}
this.widgetId = turnstile.render(containerId, {
sitekey: this.sitekey,
callback: callbacks.onSuccess,
'error-callback': callbacks.onError,
'expired-callback': () => this.reset(),
})
return this.widgetId
}
reset() {
if (this.widgetId !== null) {
turnstile.reset(this.widgetId)
}
}
remove() {
if (this.widgetId !== null) {
turnstile.remove(this.widgetId)
this.widgetId = null
}
}
getToken(): string | undefined {
if (this.widgetId === null) return undefined
return turnstile.getResponse(this.widgetId)
}
}
// Usage
const manager = new TurnstileManager(SITE_KEY)
manager.render('#container', {
onSuccess: (token) => console.log('Token:', token),
onError: (error) => console.error('Error:', error),
})
适用场景:需要以编程方式控制小组件的 SPA
使用捆绑资源
脚本(scripts/)
- check-csp.sh - 验证内容安全策略是否允许 Turnstile 脚本和 iframe
使用示例:
./scripts/check-csp.sh https://example.com
参考资料(references/)
references/widget-configs.md- 所有小组件配置选项的完整参考references/error-codes.md- 全面的错误代码参考及故障排除说明references/testing-guide.md- 测试策略、虚拟密钥、E2E 模式references/react-integration.md- React 专属模式及 @marsidev/react-turnstile 用法
Claude 应在以下情况下加载这些资料:
widget-configs.md:配置小组件外观、主题或执行模式时error-codes.md:排查 100*、200*、300*、400*、600* 错误代码时testing-guide.md:设置 E2E 测试或本地开发时react-integration.md:与 React、Next.js 集成或遇到 React 特有问题时
模板(templates/)
wrangler-turnstile-config.jsonc- Cloudflare Workers 环境配置turnstile-widget-implicit.html- 隐式渲染 HTML 示例turnstile-widget-explicit.ts- 显式渲染 JavaScript APIturnstile-server-validation.ts- Siteverify API 验证函数turnstile-react-component.tsx- 使用 @marsidev/react-turnstile 的 React 组件turnstile-hono-route.ts- 带验证功能的 Hono 路由处理程序turnstile-test-config.ts- 使用虚拟密钥的测试配置
高级主题
SPA 的预先验证
Turnstile 可以签发预先验证 Cookie,使其在单页应用中跨页面导航持久有效。
turnstile.render('#container', {
sitekey: SITE_KEY,
callback: async (token) => {
// Request pre-clearance cookie
await fetch('/api/pre-clearance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
})
},
})
自定义操作和 cData
跟踪不同的质询类型或传递自定义数据:
turnstile.render('#container', {
sitekey: SITE_KEY,
action: 'login', // Track action in analytics
cdata: JSON.stringify({ userId: '123' }), // Custom data (max 255 chars)
callback: (token) => {
// Token includes action and cdata for server validation
},
})
服务端验证:
const result = await validateTurnstile(token, secretKey)
if (result.action !== 'login') {
return new Response('Invalid action', { status: 400 })
}
const customData = JSON.parse(result.cdata || '{}')
重试和错误处理策略
class TurnstileWithRetry {
private retryCount = 0
private maxRetries = 3
render(containerId: string) {
turnstile.render(containerId, {
sitekey: SITE_KEY,
retry: 'auto', // or 'never' for manual control
'retry-interval': 8000, // ms between retries
'error-callback': (error) => {
this.handleError(error)
},
})
}
private handleError(error: string) {
// Error codes that should not retry
const noRetry = ['110100', '110200', '110500']
if (noRetry.some(code => error.includes(code))) {
this.showFallback()
return
}
// Retry on transient errors
if (this.retryCount < this.maxRetries) {
this.retryCount++
setTimeout(() => {
turnstile.reset(this.widgetId)
}, 2000 * this.retryCount) // Exponential backoff
} else {
this.showFallback()
}
}
private showFallback() {
// Show alternative verification method
console.error('Turnstile failed - showing fallback')
}
}
多小组件页面
const widgets = {
login: null as string | null,
signup: null as string | null,
}
// Render multiple widgets
widgets.login = turnstile.render('#login-widget', {
sitekey: SITE_KEY,
action: 'login',
})
widgets.signup = turnstile.render('#signup-widget', {
sitekey: SITE_KEY,
action: 'signup',
})
// Reset specific widget
turnstile.reset(widgets.login)
// Get token from specific widget
const loginToken = turnstile.getResponse(widgets.login)
依赖项
必需:
- 无(Turnstile 从 CDN 加载)
可选(React):
@marsidev/[email protected]- Cloudflare 官方推荐的 React 集成[email protected]- TypeScript 类型定义
可选(其他框架):
vue-turnstile- Vue 3 集成cfturnstile-vue3- 另一种 Vue 3 封装ngx-turnstile- Angular 集成svelte-turnstile- Svelte 集成@nuxtjs/turnstile- Nuxt 全栈集成
官方文档
- Cloudflare Turnstile:https://developers.cloudflare.com/turnstile/
- 入门指南:https://developers.cloudflare.com/turnstile/get-started/
- 客户端渲染:https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/
- 服务端验证:https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
- 错误代码:https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes/
- 测试:https://developers.cloudflare.com/turnstile/troubleshooting/testing/
- 社区资源:https://developers.cloudflare.com/turnstile/community-resources/
- 从 reCAPTCHA 迁移:https://developers.cloudflare.com/turnstile/migration/recaptcha/
- Cloudflare MCP:使用
mcp__cloudflare-docs__search_cloudflare_documentation工具
软件包版本(已于 2025-10-22 验证)
{
"devDependencies": {
"@marsidev/react-turnstile": "^1.3.1",
"turnstile-types": "^1.2.3"
}
}
说明:
- @marsidev/react-turnstile 是 Cloudflare 推荐的 React 软件包
- 最近更新于 2025 年 9 月(积极维护中)
- 兼容 React 18+、Next.js 13+、Next.js 14+、Next.js 15+
生产环境示例
此技能基于生产环境中的实现:
- Cloudflare Workers:官方 HTMLRewriter 示例
- React 应用:@marsidev/react-turnstile(经 Cloudflare 验证)
- 社区:WordPress、Craft CMS、SilverStripe、Statamic 集成
- 验证:✅ 已记录并预防所有 12 个已知问题
故障排除
问题:错误 110200 - “未知域名”
解决方案:在 Cloudflare Dashboard 中将你的域名(包括用于开发的 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" />,
}))
完整设置清单
使用此清单验证你的设置:
- 已在 Cloudflare Dashboard 中创建 Turnstile 小组件
- 已添加允许的域名(包括用于开发的 localhost)
- 前端小组件从
https://challenges.cloudflare.com/turnstile/v0/api.js加载 - 小组件使用正确的 sitekey 渲染
- 已实现并测试错误回调
- 已实现服务器端 Siteverify 验证
- 密钥存储在环境变量中(未硬编码)
- 令牌验证包含 remoteip 检查
- CSP 允许 challenges.cloudflare.com(如果使用 CSP)
- 测试使用虚拟 sitekey(
1x00000000000000000000AA) - 已实现令牌过期处理(TTL 为 5 分钟)
- 已测试小组件的无障碍功能(键盘导航、屏幕阅读器)
- 错误状态会显示对用户友好的消息
- 生产部署使用与开发/预发布环境分开的组件
有问题?遇到故障?
- 查看
references/error-codes.md,了解特定错误的排查方法 - 核对 3 步设置流程中的所有步骤
- 查看官方文档:https://developers.cloudflare.com/turnstile/
- 确保已实现服务器端验证(最常见的问题)
- 使用 Cloudflare Docs MCP 工具:
mcp__cloudflare-docs__search_cloudflare_documentation
令牌效率:节省约 65%–70%(10–12k 个令牌 → 3–4k 个令牌)
可避免的错误:12 个有完整解决方案的已记录问题