别只在前端接入 Turnstile:让 AI 编程代理补齐服务端校验

2026-09-25 30 预计阅读时间: 1 分钟
来源: blog.cloudflare.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

很多网站接入 Turnstile 时,完成了前端小组件,却忘记在后端验证用户提交的 token。这样的配置看起来“能用”,实际上并没有建立完整的安全边界:机器人可以绕过页面,直接调用业务接口。

Turnstile Spin 解决的重点不是替你生成一个前端组件,而是借助你偏好的 AI 编程代理,把服务端验证接入现有项目。真正值得关注的是这条工程原则:验证码或挑战组件只能提供一个待验证凭证,最终是否放行,必须由服务器向验证服务确认。

前端成功不等于请求可信

典型流程是:

  1. 浏览器加载 Turnstile 小组件。
  2. 用户完成挑战后,前端获得一个 token。
  3. 浏览器把 token 连同表单或 API 请求发送到自己的后端。
  4. 后端使用私有密钥调用 Turnstile 的验证接口。
  5. 只有验证成功,后端才执行登录、注册、发帖或支付等业务操作。

容易出问题的是第 4 步。前端可以被修改,HTTP 请求也可以被伪造,因此后端不能依据“页面显示验证成功”、前端传来的布尔值,甚至只依据 token 是否存在来放行请求。

服务端还应检查验证响应中的必要字段,例如 success,并根据业务需要校验 hostname 或 action。验证失败时,应在业务逻辑执行之前返回错误。

可以这样接入服务端验证

下面是一个最小的 Node.js 示例。它假设前端已经把 cf-turnstile-response 放在请求体中,服务端密钥通过环境变量 TURNSTILE_SECRET_KEY 注入。

运行前安装依赖:

npm install express
export TURNSTILE_SECRET_KEY="replace-with-your-server-secret"
node server.mjs

服务端代码:

import express from "express";

const app = express();
app.use(express.json());

const secret = process.env.TURNSTILE_SECRET_KEY;
if (!secret) {
  throw new Error("TURNSTILE_SECRET_KEY is required");
}

app.post("/api/register", async (req, res) => {
  const token = req.body?.["cf-turnstile-response"];
  const remoteip = req.headers["x-forwarded-for"]?.split(",")[0]?.trim();

  if (typeof token !== "string" || token.length === 0) {
    return res.status(400).json({ error: "Turnstile token is required" });
  }

  const form = new URLSearchParams({
    secret,
    response: token,
  });

  if (remoteip) {
    form.set("remoteip", remoteip);
  }

  try {
    const verification = await fetch(
      "https://challenges.cloudflare.com/turnstile/v0/siteverify",
      {
        method: "POST",
        headers: { "content-type": "application/x-www-form-urlencoded" },
        body: form,
      },
    );

    if (!verification.ok) {
      return res.status(502).json({ error: "Turnstile verification unavailable" });
    }

    const result = await verification.json();

    if (!result.success) {
      return res.status(403).json({
        error: "Turnstile verification failed",
        codes: result["error-codes"] ?? [],
      });
    }

    // 只有通过服务端验证后,才执行创建账户等业务操作。
    return res.status(201).json({ created: true });
  } catch (error) {
    console.error("Turnstile verification error", error);
    return res.status(502).json({ error: "Unable to verify Turnstile token" });
  }
});

app.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});

可以用下面的请求验证接口是否确实要求 token:

curl -i -X POST http://localhost:3000/api/register \
  -H 'content-type: application/json' \
  --data '{}'

这个请求应该得到 400,而不是创建账户。测试真实流程时,应使用对应环境的测试凭证或由浏览器组件产生的 token,不要把生产密钥提交到代码仓库或前端资源中。

让 AI 编程代理检查完整链路

Turnstile Spin 的价值在于把“配置一个组件”扩展成“检查一次完整接入”。把任务交给 AI 编程代理时,提示词应明确服务端边界和验收条件,而不是只说“加上 Turnstile”。例如:

在当前项目的注册接口上接入 Turnstile 服务端验证。

要求:
1. 找到注册请求的后端入口和现有测试。
2. 从请求中读取 cf-turnstile-response。
3. 使用环境变量 TURNSTILE_SECRET_KEY 调用 siteverify 接口。
4. 仅当响应中的 success 为 true 时创建账户。
5. token 缺失、验证失败或验证服务不可用时,不得执行注册逻辑。
6. 不要把 secret key 暴露给浏览器、日志或提交到仓库。
7. 增加缺失 token 和验证失败的自动化测试。
8. 输出修改的文件、环境变量要求和本地验证命令。

这类提示词有两个作用。它告诉代理应该沿着哪条调用链查找代码,也把安全要求变成可以检查的验收标准。代理完成修改后,开发者仍需审阅 diff,特别是密钥读取、错误处理、请求超时、代理头部以及验证成功后才执行的业务代码。

上线前检查清单

  • 前端只负责收集 token,后端负责最终放行。
  • 服务端密钥来自环境变量或密钥管理系统。
  • 后端在创建账户、登录、提交表单等副作用发生前完成验证。
  • 验证失败和验证服务异常不会降级为“继续执行业务”。
  • 日志中不记录 secret key,也避免记录完整 token。
  • 对缺失 token、无效 token、过期 token 和验证服务超时都有测试。
  • 根据实际部署方式谨慎处理客户端 IP,不盲目信任任意 X-Forwarded-For。
  • 代理生成的改动经过人工审阅,并在真实项目测试环境中验证。

Turnstile Spin 可以减少遗漏服务端验证这类常见配置错误,但它不会替代应用本身的鉴权、限流、权限检查和输入校验。把它当作接入审查和代码修改的辅助工具,最终仍要用服务器端的明确判定保护业务入口。


相关推荐