Turnstile 验证码
Cloudflare Turnstile 是免费的 CAPTCHA 替代方案,提供更好的用户体验和更强的安全性。无需用户解决复杂的验证码谜题,即可有效防止机器人攻击。
什么是 Turnstile?
Turnstile vs 传统 CAPTCHA
传统 CAPTCHA 问题:
- 用户体验差:需要识别模糊文字、选择图片
- 可访问性差:对视障用户不友好
- 效率降低:增加表单完成时间
- 隐私担忧:Google reCAPTCHA 追踪用户
Turnstile 优势:
| 特点 | 说明 |
|---|---|
| 无需交互 | 大多数用户无需任何操作 |
| 隐私友好 | 不追踪用户行为 |
| 免费使用 | 无限制使用 |
| 易于集成 | 几行代码即可集成 |
| 高通过率 | 真实用户几乎无感知 |
工作原理
Turnstile 通过以下方式验证用户:
- 浏览器特征:分析浏览器环境
- 行为分析:检测鼠标移动、键盘输入
- 设备指纹:识别可信设备
- 机器学习:实时风险评估
用户访问 → Turnstile 分析 → 风险评估 → 通过/挑战
创建 Turnstile Widget
步骤一:访问 Turnstile 控制台
- 登录 Cloudflare 控制台
- 点击左侧菜单 "Turnstile"
- 点击 "Add site"
步骤二:配置 Widget
填写以下信息:
| 字段 | 说明 |
|---|---|
| Site name | Widget 名称,便于管理 |
| 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']);
}
配置选项
前端选项
| 选项 | 说明 | 默认值 |
|---|---|---|
| sitekey | Site Key(必需) | - |
| theme | 主题:light、dark、auto | auto |
| size | 大小:normal、compact | normal |
| callback | 验证成功回调 | - |
| expired-callback | Token 过期回调 | - |
| 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-secret | Secret Key 无效 |
| invalid-input-response | Token 无效或已过期 |
| bad-request | 请求格式错误 |
| timeout-or-duplicate | Token 已使用或超时 |
高级功能
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);
最佳实践
安全建议
- 始终后端验证:前端验证可被绕过,必须后端验证
- 保护 Secret Key:不要在客户端代码中暴露
- 设置过期时间:Token 有效期约 300 秒
- 记录失败请求:监控异常验证行为
用户体验优化
-
选择合适的模式:
- 一般网站:Managed
- 高安全要求:Non-interactive
- 无感知验证:Invisible
-
主题匹配:使用
data-theme匹配网站风格 -
错误处理:提供友好的错误提示
function onError(error) {
document.getElementById('error-message').textContent =
'验证失败,请刷新页面重试';
}
性能优化
- 异步加载脚本:使用
async defer - 延迟渲染:在用户交互时渲染
- 缓存验证结果:短时间内可缓存验证状态
常见问题
Widget 不显示?
检查:
- Site Key 是否正确
- 域名是否在允许列表中
- 脚本是否正确加载
- 控制台是否有错误
验证总是失败?
检查:
- Secret Key 是否正确
- Token 是否过期(300 秒)
- 是否重复使用 Token
- 网络连接是否正常
如何测试?
Cloudflare 提供测试密钥:
测试 Site Key:
1x00000000000000000000AA
测试 Secret Key:
1x0000000000000000000000000000000AA
测试响应:
- 总是通过
- 总是失败
- 强制交互挑战
参考资源
下一步
了解 Turnstile 后,你可以:
- 配置 Access 保护应用
- 使用 Web Analytics 监控网站
- 设置 WAF 防火墙