什么是 CAPTCHA,以及为什么你的网站需要它
CAPTCHA(全自动区分计算机与人类的公共图灵测试)是一种挑战-响应机制,旨在区分真实用户与自动化脚本。现代方案从传统的扭曲文本谜题,到不可见的行为分析,再到像 GeeTest 滑块这样的交互式拼图挑战,形式多样。
如果没有 CAPTCHA 这一层,你网站上的任何公开表单都会暴露在以下风险之下:
- 凭据填充(Credential stuffing) — 使用泄露的用户名/密码组合进行自动化登录尝试,在滥用流量中很常见。
- 垃圾提交(Spam submissions) — 机器人向联系表单、评论区或评价页面疯狂灌水。
- 账户枚举(Account enumeration) — 脚本探测有效的邮箱地址或用户名。
- 抓取(Scraping) — 自动化提取价格、库存或专有内容。
权衡点始终是 安全性 vs. 用户体验:阻力能挡住机器人,但阻力过大也会把真实用户推走。Cloudflare Turnstile 和 GeeTest v3 都是力求平衡这两点的现代选项:Turnstile 更偏向低打扰的验证,而 GeeTest v3 则通过不同类型的交互式拼图配合基于行为的检查来识别机器人。
如何获取你的 CAPTCHA 代码(Keys & Credentials)
在编写任何集成代码之前,你需要获取各提供商的专用凭据。
GeeTest V3
- 在 GeeTest 控制台创建账户。
- 进入 Captcha Dashboard 并选择 CAPTCHA v3:

- 进入可以创建新 CAPTCHA 并注册你的网站的区域(通常需要指定域名和产品类型)。
- 配置完成后,你会获得一个 CAPTCHA ID(通常称为 gt)以及用于服务端通信的私有 Key。
- 安全地存储这些值(例如放在环境变量中),让应用在不硬编码密钥的情况下也能访问。
Cloudflare Turnstile
- 登录 dash.cloudflare.com,打开 Application security > Turnstile。

- 点击 Add widget,输入你的域名,并选择一种组件模式: Managed、 Non-interactive 或 Invisible。
- 如果站点走 Cloudflare Proxy(避免重复触发 CAPTCHA),将 Pre-clearance 设置为 Yes 。
- 创建完成后,Cloudflare 会提供一个 Site Key(公开,用于客户端)和一个 Secret Key(私有,用于服务端)。
- 在 HTML 或 JavaScript 中使用 Site Key;在调用 Turnstile 验证 API 时,只从后端发送 Secret Key。
如何实现 GeeTest CAPTCHA v3:分步指南
GeeTest v3 采用多步骤流程:你的 服务器从 GeeTest 获取一个新的 challenge, 客户端使用该 challenge 渲染拼图(GeeTest API1),然后你的 服务器验证解题后的 tokens(GeeTest API2)。下面是一个 PHP + JavaScript 示例实现,用于展示这一模式;请根据 GeeTest 官方 SDK 和你选择的语言进行适配。
第 1 步 — 服务端注册端点
创建一个 PHP 脚本(例如 geetest_register.php),从 GeeTest 获取一个新的 challenge 并传给客户端:
<?php
header('Content-Type: application/json');
const CAPTCHA_ID = '07df3141a35**19a473d7c50';
const CAPTCHA_KEY = '543b19036ef**8e07d121b81e9';
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
function getJson($url) {
$res = @file_get_contents($url);
return $res ? json_decode($res, true) : null;
}
// API1:初始化
if ($path === '/register') {
$data = getJson("https://api.geetest.com/register.php?gt=" . CAPTCHA_ID . "&json_format=1");
echo json_encode($data ? [
'gt' => CAPTCHA_ID,
'challenge' => $data['challenge'],
'success' => $data['success'] === 1,
'new_captcha' => true
] : ['success' => 0]);
exit;
}
// API2:验证
if ($path === '/validate' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$req = json_decode(file_get_contents('php://input'), true);
$data = getJson("https://api.geetest.com/validate.php?" . http_build_query([
'seccode' => $req['geetest_seccode'] ?? '',
'challenge' => $req['geetest_challenge'] ?? '',
'gt' => CAPTCHA_ID,
'json_format' => 1
]));
echo json_encode(['success' => !empty($data['seccode'])]);
exit;
}
http_response_code(404);
echo json_encode(['error' => 'Not found']);
?>
第 2 步 — 客户端初始化
加载 GeeTest SDK,并在调用 initGeetest 时传入来自服务端(API1)的参数。以下是使用 ajax 的示例:
ajax({
url: "https://example.com/register",
type: "get",
dataType: "json",
success: function (data) {
initGeetest({
gt: data.gt,
challenge: data.challenge,
offline: !data.success,
new_captcha: true
}, function (captchaObj) {
captchaObj.appendTo("#captcha");
captchaObj.onSuccess(function () {
const result = captchaObj.getValidate();
ajax({
url: "https://example.com/validate",
type: "post",
contentType: "application/json",
data: JSON.stringify(result),
success: function(res) {
if (res.success) alert('CAPTCHA passed');
else alert('CAPTCHA failed');
}
});
});
});
}
});
检查运行情况
请确认:
- /register 会返回 challenge
- captcha 能正确显示
- 通过 captcha 后,在浏览器控制台可以看到对 /validate 的请求
- 服务端返回 "success": true
Failback(fallback 模式)
如果 GeeTest 服务器不可用:
- 客户端会收到 success: false
- captcha 会切换到 local mode(无需连接 GeeTest Cloud 也能工作)。要测试这一点,只需替换为错误的 CAPTCHA_ID (例如 123456789)。
Note: GeeTest 也为不同平台和语言提供官方服务端 SDK —— 你可以选择与技术栈匹配的版本。更多信息见 GeeTest CAPTCHA v3 官方文档。
如何实现 Cloudflare Turnstile:分步指南
Cloudflare Turnstile 不需要单独的服务端“注册调用”。你只需在页面中嵌入一个组件,让它生成 token,然后在服务端通过 Cloudflare 的 Siteverify API 验证该 token。
第 1 步 — 引入 Turnstile 脚本
自动渲染(页面加载时自动创建组件):
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
编程式控制(通过 JavaScript 自行创建组件):
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" defer></script>
重要提示:脚本必须从精确的 URL 加载。代理或缓存可能导致失败。
第 2 步 — 为组件创建容器
自动:
<div class="cf-turnstile" data-sitekey="<YOUR_SITEKEY>"></div>
编程式:
<div id="turnstile-container"></div>
第 3 步 — 组件配置
通过 data attributes:
<div class="cf-turnstile" data-sitekey="<YOUR_SITEKEY>" data-theme="light" data-size="normal" data-callback="onSuccess"> </div>
通过 JavaScript:
const widgetId = turnstile.render("#turnstile-container", {
sitekey: "<YOUR_SITEKEY>",
theme: "light",
size: "normal",
callback: token => console.log("Token:", token)
});
第 4 步 — 使用 tokens
const token = turnstile.getResponse(widgetId); // 获取 token
const isExpired = turnstile.isExpired(widgetId); // 检查是否过期
turnstile.reset(widgetId); // 重置
turnstile.remove(widgetId); // 移除
turnstile.execute("#turnstile-container"); // 手动执行
第 5 步 — 与表单集成
<form id="my-form" method="POST"> <input type="hidden" name="cf-turnstile-response" id="cf-turnstile-response"> <button type="submit">Submit</button> </form>
<script>
function onSuccess(token) {
document.getElementById("cf-turnstile-response").value = token;
}
</script>
完整代码示例
<html>
<head>
<title>Turnstile Example</title> <!-- 引入 Turnstile 脚本 -->
<script src="https: //challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<h1>Form example with Turnstile</h1>
<form id="my-form"> <label for="username">Name:</label> <input type="text" name="username" id="username" required> <!-- Turnstile 容器 -->
<div class="cf-turnstile" data-sitekey="<YOUR_SITEKEY>" data-callback="onTurnstileSuccess"></div> <button type="submit">Submit</button>
</form>
<script>
// 通过 CAPTCHA 后触发的回调
function onTurnstileSuccess(token) {
console.log("Received Turnstile token:", token);
// 将 token 保存到隐藏表单字段(可选)
document.getElementById("cf-turnstile-token")?.remove();
const input = document.createElement("input");
input.type = "hidden";
input.name = "cf-turnstile-response";
input.id = "cf-turnstile-token";
input.value = token;
document.getElementById("my-form").appendChild(input);
}
// 表单提交
document.getElementById("my-form").addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const response = await fetch("/submit-form", {
method: "POST",
body: formData
});
const result = await response.json();
if(result.success){
alert("Form successfully submitted and token verified!");
} else {
alert("Turnstile token verification error. Please try again.");
// 重置组件,让用户可以再次完成 CAPTCHA
turnstile.reset();
}
});
</script>
</body>
</html>
第 6 步 — 配置服务端部分
服务端验证流程:
- 客户端:用户在页面上完成 Turnstile → 生成 token。
- 提交表单:token 会与表单数据一起发送到服务器。
- 服务器:携带 token 和 secret 向 Cloudflare Siteverify API 发起 POST 请求。
- Cloudflare:返回包含结果(success: true/false)及附加信息(action、hostname、completion time)的 JSON。
- 服务器:决定允许还是拒绝用户操作。
Siteverify API:
POST
https://challenges.cloudflare.com/turnstile/v0/siteverify
请求参数:
- secret (required):Cloudflare 面板中的 Turnstile secret key
- response (required):客户端收到的 token
- remoteip (optional):用户的 IP 地址(推荐)
- idempotency_key (optional):用于防止重复验证的唯一 UUID
Token 属性:
- 最大长度:2048 字符
- 有效期 5 分钟
- 单次使用
- 过期或重复验证时,API 将返回 timeout-or-duplicate 错误
PHP 验证示例:
<?php
function validateTurnstile($token, $secret, $remoteip = null) {
$url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
$data = ['secret' => $secret,
'response' => $token];
if ($remoteip) $data['remoteip'] = $remoteip;
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded
",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$response = file_get_contents($url, false, stream_context_create($options));
if ($response === FALSE) {
return ['success' => false,
'error-codes' => ['internal-error']];
}
return json_decode($response, true);
}
// 用法
$secret_key = 'YOUR_SECRET_KEY';
$token = $_POST['cf-turnstile-response'] ?? '';
$remoteip = $_SERVER['REMOTE_ADDR'];
$result = validateTurnstile($token, $secret_key, $remoteip);
if ($result['success']) {
echo "Form successfully submitted!";
} else {
echo "Verification error: " . implode(', ', $result['error-codes']);
}
?>
Note: 关于安装与配置 Cloudflare Turnstile 的全部详细说明(包括客户端与服务端集成、代码示例、错误说明与安全建议),请参阅 Cloudflare 官方文档。
自定义 CAPTCHA:何时以及为什么
自定义 captcha 是指你自行构建的挑战-响应系统,而不是集成第三方服务。常见的轻量方案包括:
- 数学题 — 例如“\(4 + 9\) 等于多少?”以纯文本或简单生成图片的形式呈现。
- Honeypot 字段 — 隐藏表单字段,通常只有机器人会去填写。
- 基于时间的检查 — 拒绝“以人类不可能的速度”完成的提交。
- 自定义图片挑战 — 使用你自己的素材做“点击不属于该组的元素”等题目。
当你需要严格的品牌控制、当严格的隐私要求限制第三方脚本、或当你的风险较低且只需要基本的机器人阻力时,自定义 captcha 可能会有用。不过,它们通常缺乏 GeeTest 或 Turnstile 这类服务的复杂行为模型,也更容易被高级机器人攻破。
你还需要对 无障碍(例如提供音频替代方案)以及随着机器人技术演进而持续的 维护承担全部责任。对于登录、结账、重置密码等关键流程,托管方案通常比完全自定义 captcha 更安全。
如何更有效地拦截 CAPTCHA 机器人
实现 CAPTCHA 是必要的,但单靠它往往并不够;有决心的攻击者会尝试绕过或自动化它。要更有效地阻止 captcha 机器人,请叠加多层防护。
第 1 层 — 基础设施层面的限流
在 Web 服务器或 CDN 上进行限流,即使机器人能解题或绕过 captcha,也会更难对表单进行暴力尝试。例如,在 Nginx 中:
# Nginx:将登录端点限制为每个 IP 每秒 10 个请求
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/s;
location /login {
limit_req zone=login burst=5 nodelay;
# proxy_pass ...
}
第 2 层 — Honeypot 字段
一个真实用户永远看不到、但不够“聪明”的机器人可能会填写的隐藏字段,是一种低打扰的防御方式。
<div aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
在服务端,拒绝任何 website 非空的提交。这样几乎不会增加 UX 成本,同时能过滤掉一部分自动化提交。
第 3 层 — 其他防护层
- IP 信誉过滤:在边缘侧封禁已知的数据中心网段或匿名代理服务,可显著减少滥用流量。
- 防止 token 重放:Turnstile token 必须在服务端验证并按单次使用处理;丢弃被重复使用的 token 有助于防止重放攻击。
- 设备或会话指纹:外部工具可帮助跨 IP 与会话关联可疑模式,但会带来隐私方面的考量。
- 监控与告警:跟踪验证失败率、特定地区或 ASN 流量的突然激增,把它们作为攻击进行中的信号。