如何在你的网站上实现 CAPTCHA:分步指南

机器人早已是你流量的一部分——不管你是否察觉。它们创建虚假账号、测试被盗密码,并在任何人高喊“事故”之前就悄悄给你的后端施加压力。理解在你的网站上如何实现 captcha不只是“可有可无”;它决定了你是把滥用当作事后才处理的问题,还是把防护直接融入你的表单、流程和基础设施之中。
本指南将带你完成两种可用于生产环境的实现:GeeTest CAPTCHA v3 和 Cloudflare Turnstile。在此过程中,你还将了解如何获取 captcha 代码(站点密钥和凭据)、何时需要自定义 captcha、如何阻止 captcha 绕过尝试,以及如何使用 CapMonster Cloud 验证你的集成。
什么是 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 流量的突然激增,把它们作为攻击进行中的信号。
使用 CapMonster Cloud 测试你的 CAPTCHA 集成
当你的 CAPTCHA 上线后,你需要一种可靠的方法,在自动化测试流水线中覆盖完整的请求-响应闭环——而不必在每次 CI 运行时手动解谜。 CapMonster Cloud 是一个基于 API 的 CAPTCHA 求解服务,支持 GeeTest v3 和 Cloudflare Turnstile 等多种类型。你提交一个任务来描述需要被求解的 captcha,API 会返回解决方案 token,你的测试可以将其直接注入到表单提交中。
两种 CAPTCHA 类型的完整流程都由相同的三个步骤组成: 创建任务 → 轮询结果 → 使用解决方案。
GeeTest V3 — CapMonster Cloud 完整流程
第 1 步 — 创建任务
从你的 GeeTest 注册端点获取一个新的 challenge 后,立刻调用 createTask。 challenge 值的有效期很短且只能使用一次。
请求:
POST https://api.capmonster.cloud/createTask
{
"clientKey": "YOUR_CAPMONSTER_API_KEY",
"task": {
"type":"GeeTestTask",
"websiteURL": "https://example.com/your-page",
"gt":"022397c99c9f646f6477822485f30404",
"challenge":"7f044f48bc951ecfbfc03842b5e1fe59",
"geetestApiServerSubdomain":"api-na.geetest.com"
}
}响应:
{
"errorId": 0,
"taskId": 407533072
}第 2 步 — 轮询结果
循环调用 getTaskResult,每次轮询之间等待几秒。GeeTest V3 任务通常会在 10–30 秒内完成,具体取决于系统负载。
请求:
POST https://api.capmonster.cloud/getTaskResult
{
"clientKey": "YOUR_CAPMONSTER_API_KEY",
"taskId": 407533072
}解题进行中时的响应:
{
"errorId":0,
"status":"processing"
}就绪时的响应:
{
"errorId":0,
"status":"ready",
"solution":{
"challenge":"0f759dd1ea6c4wc76cedc2991039ca4f23",
"validate":"6275e26419211d1f526e674d97110e15",
"seccode":"510cd9735583edcb158601067195a5eb|jordan"
}
}solution 中的三个值—— challenge、 validate 和 seccode——必须一起提交给你的后端,完全按照真实浏览器发送它们的方式提交。
第 3 步 — 提交解决方案
将三个解决方案值 POST 到你的表单端点,并使用你的 GeeTest 集成所期望的相同请求体字段:
const result = await pollForResult(taskId);
await fetch('/submit_form.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
geetest_challenge: result.solution.challenge,
geetest_validate: result.solution.validate,
geetest_seccode: result.solution.seccode,
})
});Cloudflare Turnstile — CapMonster Cloud 完整流程
第 1 步 — 创建任务
请求:
POST https://api.capmonster.cloud/createTask
{
"clientKey": "API_KEY",
"task": {
"type": "TurnstileTask",
"websiteURL": "http://tsmanaged.zlsupport.com",
"websiteKey": "0x4AAAAAAABUYP0XeMJF0xoy"
}
}关于参数说明的详细信息,请参阅 CapMonster 文档。
响应:
{
"errorId": 0,
"taskId": 407533072
}第 2 步 — 轮询结果
Turnstile 任务通常会在 5–20 秒内完成。
请求:
POST https://api.capmonster.cloud/getTaskResult
{
"clientKey": "YOUR_CAPMONSTER_API_KEY",
"taskId": 407533072
}解题进行中时的响应:
{
"errorId": 0,
"status": "processing"
}就绪时的响应:
{
"errorId": 0,
"status": "ready",
"solution": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
"token": "0.iGX3xsyFCkbGePM3jP4P4khLo6TrLukt8ZzBvwuQOvbC...f61f3082"
}
}solution.token 是在提交表单时填入 cf-turnstile-response 的值。在你的 HTTP 客户端或 Playwright/Selenium 实例中使用 solution.userAgent,以匹配 token 被求解时所处的环境——环境不一致可能导致服务端拒绝。
第 3 步 — 提交解决方案
在提交表单之前注入已求解的 token——可以直接写入隐藏 input,或通过 Cloudflare 的 JavaScript 回调注入:
// 选项 A:直接向 DOM 注入(用于 Playwright/Selenium 测试)
await page.evaluate(token => {
const field = document.querySelector('[name="cf-turnstile-response"]');
if (field) field.value = token;
}, result.solution.token);
// 选项 B:直接触发 Turnstile 回调(用于 SPA 测试)
await page.evaluate(token => {
if (window.turnstileCallback) window.turnstileCallback(token);
}, result.solution.token);
await page.click('#submit-btn');CapMonster Cloud 错误处理
createTask 和 getTaskResult 使用相同的错误封装格式。 errorId 为 0 始终表示成功; errorId: 1 表示出现问题,具体的错误类型会在字符串字段 errorCode 中返回。关于可能出现的错误完整列表,请参阅 CapMonster 文档。
错误响应示例:
{
"errorId": 1,
"errorCode": "ERROR_KEY_DOES_NOT_EXIST"
}最佳实践与提示
优雅处理 Token 过期
GeeTest 的 challenge 和 Turnstile 的 token 都有时间限制,因此如果用户在提交表单前等待太久,你应预期会偶尔过期。实现 Turnstile 的 expired-callback 等回调,或重新获取一个新的 GeeTest challenge 并重置组件,而不是静默失败或抛出通用错误。
无障碍(WCAG 2.1)
Cloudflare Turnstile 提供视觉挑战和替代挑战流程,旨在与辅助技术配合使用,但你应该使用用户依赖的屏幕阅读器和浏览器进行测试。
GeeTest v3 的滑块拼图以鼠标/触控为主,这可能会给某些存在运动或协调障碍的用户带来挑战;在需要强无障碍支持的场景中,考虑提供替代验证路径。
避免在面向残障用户的关键流程中把 CAPTCHA 作为唯一保护机制;应将其与限流、异常检测等其他防护结合使用。
隐私与第三方数据
使用 Turnstile 或 GeeTest 意味着将请求与交互数据发送给第三方提供商进行分析与验证。你的隐私政策与同意机制应如实反映这一点,尤其是在 GDPR 或 CCPA 等框架下,此类处理可能被视为个人数据处理。
策略性地应用 CAPTCHA —— 不要到处都用
区分测试与生产环境的 Key
Cloudflare 和大多数 CAPTCHA 提供商都建议为开发、预发布(staging)和生产使用不同的 key 或环境。请将你的 Secret Key 以及任何等价的私有 key 存放在环境变量或专用的密钥管理器中,而不是硬编码到代码里。
保持 SDK 与集成更新
CAPTCHA 服务会随着时间推移更新其检测逻辑与 API,以应对新的攻击技术。定期查看提供商的变更日志或文档并更新你的集成,有助于避免使用已弃用的端点或落入被弱化的配置。
结语
正确理解并掌握 如何实现 CAPTCHA,不只是把一个组件塞进 HTML 这么简单;更重要的是,将稳健的客户端流程与可靠的服务端验证配对,并把二者纳入更广泛的防滥用策略中。GeeTest v3 提供基于行为的拼图挑战,可以提高机器人操作者的门槛;而 Cloudflare Turnstile 能自然融入 Cloudflare 驱动的网站,并强调低打扰验证。
无论你选择哪种方案,将 CAPTCHA 与限流、honeypot 字段和 IP 信誉过滤结合使用,都会显著提高滥用表单所需的成本。而当你需要测试集成或自动化复杂工作流时, CapMonster Cloud 通过一个简单的 API,为你提供了可编程的方式来求解 GeeTest 与 Cloudflare Turnstile 挑战——让你更容易在应用演进的同时持续确保防护有效。
准备把自动化 CAPTCHA 测试加入你的工具箱了吗? 探索 CapMonster Cloud,并立即开始把他们的 GeeTest 与 Turnstile 任务类型集成到你的开发与 QA 工作流中。






