跳到主要内容

Turnstile 验证码

Cloudflare Turnstile 是免费的 CAPTCHA 替代方案,提供更好的用户体验和更强的安全性。无需用户解决复杂的验证码谜题,即可有效防止机器人攻击。

什么是 Turnstile?

Turnstile vs 传统 CAPTCHA

传统 CAPTCHA 问题

  • 用户体验差:需要识别模糊文字、选择图片
  • 可访问性差:对视障用户不友好
  • 效率降低:增加表单完成时间
  • 隐私担忧:Google reCAPTCHA 追踪用户

Turnstile 优势

特点说明
无需交互大多数用户无需任何操作
隐私友好不追踪用户行为
免费使用无限制使用
易于集成几行代码即可集成
高通过率真实用户几乎无感知

工作原理

Turnstile 通过以下方式验证用户:

  1. 浏览器特征:分析浏览器环境
  2. 行为分析:检测鼠标移动、键盘输入
  3. 设备指纹:识别可信设备
  4. 机器学习:实时风险评估
用户访问 → Turnstile 分析 → 风险评估 → 通过/挑战

创建 Turnstile Widget

步骤一:访问 Turnstile 控制台

  1. 登录 Cloudflare 控制台
  2. 点击左侧菜单 "Turnstile"
  3. 点击 "Add site"

步骤二:配置 Widget

填写以下信息:

字段说明
Site nameWidget 名称,便于管理
Domain允许使用 Widget 的域名
Widget Mode验证模式

Widget Mode 选项

模式说明适用场景
Managed自动选择最佳验证方式推荐,大多数网站
Non-interactive完全无交互高安全要求
Invisible完全隐藏需要无感知验证

步骤三:获取密钥

创建后获得:

  • Site Key:前端使用,公开
  • Secret Key:后端验证使用,保密

重要:妥善保管 Secret Key,不要泄露!

前端集成

HTML 集成

方法一:自动渲染

<!DOCTYPE html>
<html>
<head>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form action="/submit" method="POST">
<input type="email" name="email" placeholder="Email">
<input type="password" name="password" placeholder="Password">

<!-- Turnstile Widget -->
<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-theme="light"
data-callback="onSuccess">
</div>

<button type="submit">Submit</button>
</form>

<script>
function onSuccess(token) {
console.log('Challenge passed:', token);
}
</script>
</body>
</html>

方法二:显式渲染

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<div id="turnstile-widget"></div>

<script>
window.onloadTurnstileCallback = function () {
turnstile.render('#turnstile-widget', {
sitekey: 'YOUR_SITE_KEY',
theme: 'light',
callback: function(token) {
console.log('Challenge passed:', token);
},
});
};
</script>

React 集成

import { useEffect, useRef } from 'react';

function Turnstile({ siteKey, onVerify }) {
const containerRef = useRef(null);
const widgetIdRef = useRef(null);

useEffect(() => {
if (!window.turnstile) {
const script = document.createElement('script');
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
script.async = true;
script.defer = true;
document.head.appendChild(script);

script.onload = () => {
renderWidget();
};
} else {
renderWidget();
}

return () => {
if (widgetIdRef.current && window.turnstile) {
turnstile.remove(widgetIdRef.current);
}
};
}, []);

const renderWidget = () => {
if (containerRef.current && window.turnstile) {
widgetIdRef.current = turnstile.render(containerRef.current, {
sitekey: siteKey,
callback: onVerify,
});
}
};

return <div ref={containerRef}></div>;
}

export default Turnstile;

使用示例

function LoginForm() {
const [token, setToken] = useState(null);

const handleSubmit = async (e) => {
e.preventDefault();

if (!token) {
alert('Please complete the verification');
return;
}

const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({
email: e.target.email.value,
password: e.target.password.value,
token: token
}),
});
};

return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" />
<input name="password" type="password" />
<Turnstile siteKey="YOUR_SITE_KEY" onVerify={setToken} />
<button type="submit">Login</button>
</form>
);
}

Vue 集成

<template>
<div ref="turnstileContainer"></div>
</template>

<script>
export default {
props: {
siteKey: String,
},
emits: ['verify'],
mounted() {
this.loadTurnstile();
},
methods: {
loadTurnstile() {
if (window.turnstile) {
this.renderWidget();
return;
}

const script = document.createElement('script');
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
script.async = true;
script.defer = true;
script.onload = () => this.renderWidget();
document.head.appendChild(script);
},
renderWidget() {
if (this.$refs.turnstileContainer && window.turnstile) {
turnstile.render(this.$refs.turnstileContainer, {
sitekey: this.siteKey,
callback: (token) => this.$emit('verify', token),
});
}
},
},
};
</script>

后端验证

Node.js 验证

const express = require('express');
const app = express();

app.use(express.json());

app.post('/api/submit', async (req, res) => {
const { token } = req.body;

if (!token) {
return res.status(400).json({ error: 'Token is required' });
}

// 验证 Turnstile Token
const formData = new URLSearchParams();
formData.append('secret', 'YOUR_SECRET_KEY');
formData.append('response', token);

const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: formData,
});

const outcome = await result.json();

if (outcome.success) {
// 验证成功,处理请求
res.json({ success: true, message: 'Verification passed' });
} else {
// 验证失败
res.status(400).json({ error: 'Verification failed', codes: outcome['error-codes'] });
}
});

app.listen(3000);

Python 验证

import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/submit', methods=['POST'])
def verify_turnstile():
token = request.json.get('token')

if not token:
return jsonify({'error': 'Token is required'}), 400

# 验证 Turnstile Token
response = requests.post(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
data={
'secret': 'YOUR_SECRET_KEY',
'response': token,
}
)

result = response.json()

if result.get('success'):
return jsonify({'success': True, 'message': 'Verification passed'})
else:
return jsonify({
'error': 'Verification failed',
'codes': result.get('error-codes', [])
}), 400

if __name__ == '__main__':
app.run()

PHP 验证

<?php

function verifyTurnstile($token) {
$secret = 'YOUR_SECRET_KEY';

$data = [
'secret' => $secret,
'response' => $token,
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);

return $result['success'] ?? false;
}

// 使用示例
$token = $_POST['token'] ?? '';

if (verifyTurnstile($token)) {
// 验证成功
echo json_encode(['success' => true]);
} else {
// 验证失败
echo json_encode(['error' => 'Verification failed']);
}

配置选项

前端选项

选项说明默认值
sitekeySite Key(必需)-
theme主题:light、dark、autoauto
size大小:normal、compactnormal
callback验证成功回调-
expired-callbackToken 过期回调-
error-callback错误回调-
language语言代码自动检测

示例

<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-theme="dark"
data-size="compact"
data-language="zh-CN"
data-callback="onSuccess">
</div>

后端验证响应

成功响应

{
"success": true,
"challenge_ts": "2024-01-01T00:00:00Z",
"hostname": "example.com",
"error-codes": [],
"action": "login",
"cdata": "session123"
}

失败响应

{
"success": false,
"error-codes": ["invalid-input-response"]
}

错误代码

错误代码说明
invalid-input-secretSecret Key 无效
invalid-input-responseToken 无效或已过期
bad-request请求格式错误
timeout-or-duplicateToken 已使用或超时

高级功能

Action 参数

为不同操作设置不同的验证级别:

<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-action="login">
</div>

常见 action

  • login:登录
  • register:注册
  • contact:联系表单
  • comment:评论

自定义数据

传递自定义数据用于验证:

<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-cdata="user_session_123">
</div>

重置 Widget

// 重置并获取新的 token
turnstile.reset(widgetId);

移除 Widget

// 移除 Widget
turnstile.remove(widgetId);

最佳实践

安全建议

  1. 始终后端验证:前端验证可被绕过,必须后端验证
  2. 保护 Secret Key:不要在客户端代码中暴露
  3. 设置过期时间:Token 有效期约 300 秒
  4. 记录失败请求:监控异常验证行为

用户体验优化

  1. 选择合适的模式

    • 一般网站:Managed
    • 高安全要求:Non-interactive
    • 无感知验证:Invisible
  2. 主题匹配:使用 data-theme 匹配网站风格

  3. 错误处理:提供友好的错误提示

function onError(error) {
document.getElementById('error-message').textContent =
'验证失败,请刷新页面重试';
}

性能优化

  1. 异步加载脚本:使用 async defer
  2. 延迟渲染:在用户交互时渲染
  3. 缓存验证结果:短时间内可缓存验证状态

常见问题

Widget 不显示?

检查:

  1. Site Key 是否正确
  2. 域名是否在允许列表中
  3. 脚本是否正确加载
  4. 控制台是否有错误

验证总是失败?

检查:

  1. Secret Key 是否正确
  2. Token 是否过期(300 秒)
  3. 是否重复使用 Token
  4. 网络连接是否正常

如何测试?

Cloudflare 提供测试密钥:

测试 Site Key

1x00000000000000000000AA

测试 Secret Key

1x0000000000000000000000000000000AA

测试响应

  • 总是通过
  • 总是失败
  • 强制交互挑战

参考资源

下一步

了解 Turnstile 后,你可以: