8.8 KiB
Cloudflare Turnstile 网站接入指南
本文说明如何在任意网站中集成 Cloudflare Turnstile 人机验证:从控制台拿密钥、前端展示控件、把一次性 token 交给后端、由服务端向 Cloudflare 校验。内容适用于自建站点与 API,不限定具体技术栈,文末可对照本仓库中的实现。
1. 概念与流程
| 项 | 说明 |
|---|---|
| Site Key | 公钥,可出现在前端代码或 HTML 中。 |
| Secret Key | 私钥,仅能用于你的服务端调用校验接口。 |
| Token(response) | 用户通过验证后,Turnstile 在浏览器中生成的一串临时字符串,需在你自己的业务请求里随表单/JSON 提交到你的后端。 |
| 二次校验 | 前端 token 不能单独作为“已通过人机验证”的依据;必须由服务器使用 Secret Key 调用 Cloudflare 的 siteverify 接口确认。 |
典型流程:
- 页面加载 Cloudflare 提供的 JS,在指定 DOM 中渲染小组件。
- 用户通过挑战后,前端拿到
token,写入状态或随登录/注册请求提交。 - 你的后端在执行业务逻辑之前用 Secret Key +
token(及可选的客户端 IP)请求siteverify;success: true才继续。 - 每个 token 仅应用一次、且有时效;提交失败后应重置控件以获取新 token(见第 3 节)。
2. 在 Cloudflare 控制台创建站点
- 登录 Cloudflare Dashboard,进入 Turnstile。
- 选择 Add widget / 添加站点。
- 填写网站域名、选择 Managed(或按需选择可见性/交互方式)。
- 创建后得到 Site Key 与 Secret Key;为生产环境可单独为生产域名建 widget,勿把 Secret Key 写进仓库或静态资源。
本地开发:在 Turnstile 站点里把 localhost 及测试域名加入允许的主机名(与 Cloudflare 当前 UI 中「主机名」/域名列表一致),否则可能无法加载或校验失败。
3. 前端:加载脚本与渲染控件
3.1 官方脚本
推荐按文档使用官方脚本,例如显式渲染(render=explicit)便于在单页应用里把控件挂在指定容器上:
- 脚本地址:
https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit
在页面中插入一次即可(注意避免重复注入同一脚本多份,除非你有意为之)。
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
defer
></script>
全局会暴露 window.turnstile,主要方法包括:
turnstile.render(container, options):在container(DOM 元素或选择器)内渲染,返回widgetId。turnstile.reset(widgetId):同一会话内换发新 token(失败重试、重复提交时很有用)。turnstile.remove(widgetId):销毁实例。
3.2 常用 render 参数
| 参数 | 含义 |
|---|---|
sitekey |
必填,即 Site Key。 |
callback |
验证通过时回调,参数为 token 字符串。 |
expired-callback |
token 过期时。 |
error-callback |
发生错误时。 |
theme |
可选 light / dark / auto 等。 |
建议在 expired-callback 与 error-callback 里把本地产出的 token 清空,避免用失效 token 提交。
3.3 把 token 交给你的后端
验证通过后,在 callback 中保存 token(如 React 的 state、Vue 的 ref)。用户点击「登录/提交」时,将 同一请求 中的其它字段与 turnstileToken(或你命名的字段名)一起发给你自己的 API。字段名仅前后端一致即可,例如 JSON:
{
"username": "user",
"password": "…",
"turnstileToken": "0.xxx…"
}
注意:
- 未启用 Turnstile 的页面或测试环境,前后端要约定好是否“可不传 token”,避免生产误关校验。
- 提交后若失败,应调用
reset并清空本地 token,避免复用。
3.4 与 Content Security Policy (CSP) 的兼容
若站点启用了严格 CSP,需允许 Cloudflare 相关源,例如(按实际策略合并):
script-src:https://challenges.cloudflare.comframe-src或child-src:同上(部分实现会以 iframe 呈现挑战)- 有时还需
https://challenges.cloudflare.com/cdn-cgi/...等,以浏览器控制台与官方文档为准。
4. 后端:siteverify 校验
在执行业务逻辑之前用服务器端向 Cloudflare 验证 token。不应信任客户端声称的“已验证”。
4.1 端点
POST https://challenges.cloudflare.com/turnstile/v0/siteverify
使用 application/x-www-form-urlencoded 或等价的表单编码提交。
4.2 参数
| 参数 | 必填 | 说明 |
|---|---|---|
secret |
是 | Secret Key。 |
response |
是 | 前端传来的 token。 |
remoteip |
否 | 发起请求的用户 IP,可与 Cloudflare 侧风控一致,建议有则传。 |
4.3 响应
JSON 中至少包含 success(布尔值)。success 为 true 才应继续登录、注册、发帖等业务。
失败时 JSON 中常带有 error-codes 数组,便于排障,例如 invalid-input-response、timeout-or-duplicate 等(以官方说明为准)。
实践建议:
- 为 HTTP 客户端设置合理超时(如 8~15 秒),失败时对用户显示通用错误,不要在错误信息中泄露 Secret Key 或内部堆栈。
siteverify应仅在服务端调用;不要从浏览器直接带 Secret 请求。
4.4 伪代码示例
if secret_key is empty: return 500 # 未配置
if user_token is empty: return 400 # 请完成人机验证
POST form: secret, response, (optional) remoteip
if JSON.success != true: return 400 # 验证失败
# 再执行登录/注册等逻辑
语言无关:Go 可用 http.PostForm,Node 可用 fetch + URLSearchParams,Python 可用 httpx/requests 的 data= 等。
5. 安全与产品注意点
- Site Key 可公开,Secret Key 绝不可进前端、公开仓库、日志。 使用环境变量或密钥管理。
- 每次敏感操作若需要防护,可要求新的 token,而不是长缓存同一条 token。
- 与速率限制、账户锁定、异常 IP 检测等组合,而不是只依赖 Turnstile。
- 若使用反向代理,
remoteip应取真实客户端 IP(与 Turnstile 可见一致),与信任代理头配置统一。 - 多环境(dev/staging/prod)使用不同 widget 与密钥,避免误用。
6. 排障简表
| 现象 | 可能原因 |
|---|---|
| 前端不显示控件 | 域名未在 Turnstile 站点中、脚本被 CSP/广告拦截、脚本未加载完就 render |
总是 error-callback |
Site Key 错误、域名校验失败、网络问题 |
siteverify 失败 |
token 已用过/过期、Secret Key 错误、时间偏差过大、请求不是 POST 表单 |
| 生产正常、本地失败 | 未把 localhost 加入允许主机名、混用不同环境的 Key |
更完整的错误码与行为以 Cloudflare 官方文档为准:
https://developers.cloudflare.com/turnstile/
7. 与本项目(SproutGate)的对应关系(参考)
本仓库中可作为对照的实现位置(不保证与上文逐字相同,以代码为准):
- 前端组件:
sproutgate-frontend/src/components/oauth/TurnstileWidget.jsx(显式render、token 回调、reset)。 - 前端在登录/注册中附带
turnstileToken:UserPortal.jsx、UserPortalAuthSection.jsx。 - 后端校验:
sproutgate-backend/internal/handlers/turnstile.go中verifyTurnstileToken及turnstileVerifyURL。 - 管理端配置 Site/Secret 与开关:
/api/admin/turnstile、存储层TurnstileConfig。
第三方站点只需遵守本文第 1~5 节的通用步骤即可;具体路由与配置项以各自系统为准。