六维教程

验证码基础与前端嵌入

表单防刷、注册防爬、登录防爆破,这些场景过去基本都用 reCAPTCHA。但 reCAPTCHA 体验越来越差,又要选红绿灯又要拼图,移动端还经常卡死。Cloudflare Turnstile 是 Cloudflare 推出的无感验证码服务,目标是替代 reCAPTCHA,绝大多数情况下用户什么都不用做就通过验证。这篇先把 Turnstile 的核心概念和前端嵌入讲清楚,后端校验留到下一篇。

Turnstile 与 reCAPTCHA 的区别

Turnstile 不依赖用户点击或拼图,而是通过浏览器环境信号判断访客是不是真人。两者对比

对比项 reCAPTCHA Turnstile
验证方式 选图、拼图、勾选复选框 多数情况无感,少数才弹交互挑战
用户操作 经常需要点击 多数零点击
隐私 依赖 Google 行为画像 不依赖第三方画像,数据不留存
品牌 Google Cloudflare
免费额度 有量级限制 无限免费
国内可用性 经常加载失败 通过 Cloudflare 边缘节点,更稳定
是否要 Google 账号 不要

最直观的感受是 Turnstile 加载快、不卡用户。后台逻辑是它收集浏览器指纹、JS 挑战、TLS 指纹等一系列信号做综合判断,可信访客直接放行,可疑的才需要交互。

站点密钥与密钥

每个 Turnstile 站点会生成一对密钥

密钥类型 英文名 用途 是否公开
站点密钥 sitekey 嵌入前端 HTML,标识是哪个站点 公开,可写在前端
密钥 secret key 后端校验 token 时携带 严禁泄露

站点密钥写在前端没问题,它本身不能验证任何东西,只是告诉 Turnstile 这次的挑战属于哪个站点。密钥才是真正用来校验的凭证,一旦泄露别人就能伪造校验结果,必须放在服务端。

在 Cloudflare 控制台左侧找到 Turnstile,点击添加站点,填入域名后会立即拿到这对密钥。本地开发时把 localhost 加进域名列表,否则本地测试会报域名不匹配。

Widget 的两种渲染方式

Widget 就是页面上那个验证码组件。Turnstile 提供两种渲染方式

渲染方式 说明 适用场景
显式渲染 用 JS 调用 turnstile.render 手动挂载到指定节点 单页应用、动态加载表单
隐式渲染 在 div 上加 cf-turnstile 类,脚本加载后自动渲染 传统多页站点、静态表单

两种方式的内部行为一致,区别只在挂载时机。隐式渲染简单粗暴,加个类名就行,但页面切走再切回来时组件不会自动重渲染。显式渲染更灵活,适合在 React、Vue 这类框架里精确控制生命周期。

三种渲染模式

更关键的概念是 widget 模式,它决定用户看到什么。通过站点配置或 data-attribute 指定

模式 英文名 用户感知 适用场景
托管模式 Managed 默认无感,可疑时弹交互挑战 通用场景,推荐首选
非交互模式 Non-interactive 永远不弹交互,只显示一个图标 隐私敏感、不想打扰用户
隐身模式 Invisible 完全不可见,由代码触发验证 表单提交前自动校验、登录页

托管模式是默认值,也是 Cloudflare 官方推荐的。它会动态调整难度,可信访客零交互,可疑访客才看到挑战。非交互和隐身模式牺牲一部分准确率换取体验,需要更宽松地接受可疑流量。

隐式渲染嵌入示例

最简单的嵌入方式,三步搞定。先在页面 head 引入脚本

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

在表单里放一个容器

<form action="/login" method="post">
  <input type="text" name="username" placeholder="用户名" />
  <input type="password" name="password" placeholder="密码" />
  <div class="cf-turnstile" data-sitekey="0x4AAAAAAABBBBBBBBBBBB"></div>
  <button type="submit">登录</button>
</form>

脚本加载后会扫描所有带 cf-turnstile 类的元素,自动渲染 widget。验证通过后 Turnstile 会在容器里插入一个名为 cf-turnstile-response 的隐藏 input,表单提交时随其他字段一起发到后端,这就是后端要校验的 token。

常用 data-attribute

属性 作用
data-sitekey 站点密钥,必填
data-theme 主题,light 或 dark
data-size 尺寸,normal、compact 或 flexible
data-callback 验证通过时调用的 JS 函数名
data-expired-callback token 过期时调用的 JS 函数名
data-error-callback 验证失败时调用的 JS 函数名

token 有效期大约 300 秒,过期后 widget 会自动刷新。如果你想在过期时给用户提示,就配 data-expired-callback。

显式渲染嵌入示例

单页应用更适合显式渲染。先给容器一个 id,不再加 cf-turnstile 类

<div id="turnstile-container"></div>

脚本依旧引入,但加上 ?render=explicit 参数,禁止自动渲染

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>

在需要的时候调用 turnstile.render

const widgetId = turnstile.render('#turnstile-container', {
  sitekey: '0x4AAAAAAABBBBBBBBBBBB',
  theme: 'light',
  size: 'normal',
  callback: (token) => {
    console.log('验证通过,token', token);
    document.querySelector('input[name="cf-turnstile-response"]').value = token;
  },
  'expired-callback': () => {
    console.log('token 过期,请重新验证');
    turnstile.reset(widgetId);
  },
  'error-callback': (code) => {
    console.error('验证失败,错误码', code);
  },
});

render 返回一个 widgetId,后续可以用它做 reset 或 remove。表单校验失败、用户改了密码、token 过期这类场景都要 reset 让用户重新验证,否则后端会拿到一个无效 token。

几个常见踩坑

第一,域名白名单。Turnstile 会校验当前页面域名是否在站点配置里,本地开发要把 localhost 和 127.0.0.1 都加进去。生产环境记得把所有可能的子域名都加上,比如 www.example.com 和 example.com 是分开的。

第二,脚本加载顺序。隐式渲染依赖 api.js 加载完成,如果脚本 async 但表单提前提交,可能拿不到 token。可以在表单提交时先判断隐藏 input 是否有值,没有就阻止提交并提示。

第三,token 只能用一次。后端校验成功后 token 立即失效,不能用同一个 token 校验两次。前端如果做了重试逻辑,每次重试前都要 reset 重新拿 token。

第四,size 选择。compact 尺寸适合移动端窄空间,但显示效果略差。flexible 是较新的模式,会按容器宽度自适应,体验更好但要确认浏览器兼容性。

小结

Turnstile 的前端嵌入核心是三件事,站点密钥标识站点、widget 是页面上那个验证组件、渲染模式决定用户看到什么。隐式渲染适合静态页面,加个类名就行;显式渲染适合单页应用,能精确控制挂载时机。三种模式里托管模式最常用,体验和准确率平衡最好。表单提交时记得把 cf-turnstile-response 这个隐藏字段一起带去后端,下一篇就讲后端怎么校验它。

下一篇 后端校验与集成

上一篇
Access 身份访问控制
下一篇
后端校验与集成