导读:本期聚焦于唐振业创作的《Vue 3 项目中如何优雅接入 Google reCAPTCHA 人机验证?》,敬请观看详情。表单被机器人批量提交、注册接口被恶意刷量,这类问题几乎每个做过用户系统的前端都会遇到。Google reCAPTCHA 提供了免费且可靠的人机验证能力,但官方文档以原生 JS 为主,直接照搬到 Vue 3 里容易出现脚本重复加载、组件卸载后回调丢失、TypeScript 类型缺失等一堆坑。本文从组件化封装的角度出发,先讲清楚 reCAPTCHA v2 与 v3 两种模式的差异和选型建议,再演示如何在 Vue 3 中通过 Composition API 封装一个可复用的验证组件,包括动态脚本加载、token 获取与后端校验联调的完整链路,最后补充按需卸载、防重复渲染等工程化细节,帮助你把人机验证稳定地集成到生产项目中。

人机验证是现代 Web 应用绕不开的一道防线,尤其是登录、注册、找回密码这类暴露在公网的表单接口,一旦没有验证机制,很容易被脚本批量刷请求,轻则产生大量垃圾数据,重则拖垮整个服务。Google reCAPTCHA 是目前使用最广泛的免费验证方案之一,它通过分析用户行为来判断访问者是真人还是机器,体验上比传统的图形验证码友好得多。不过在 Vue 3 项目里接入 reCAPTCHA 并不是简单地复制官方文档的几行代码就完事,脚本加载时机、组件复用、token 过期处理这些细节如果处理不好,会在生产环境埋下不少隐患。这篇文章就来完整梳理一下在 Vue 3 中工程化接入 reCAPTCHA 的思路和实践。

Vue 3 项目中如何优雅接入 Google reCAPTCHA 人机验证?

一、reCAPTCHA v2 与 v3 怎么选

Google 目前主推的版本是 reCAPTCHA v2 和 v3,两者在交互形式和适用场景上差别很大。v2 就是我们常见的那种勾选框(我不是机器人),用户需要主动完成一次交互,甚至在风控判定可疑时会弹出图片选择题。v3 则完全无感,它会在后台持续给用户行为打分,分数范围是 0.0 到 1.0,越接近 1.0 越可能是真人,整个过程中用户不会有任何感知。

选型上有个简单的判断标准:如果你的场景是纯防机器人刷接口,比如注册、发短信验证码,v3 的无感体验明显更好,用户不需要任何额外操作;如果你需要更强的确定性,比如支付确认、敏感操作二次验证,v2 的显式交互能给出明确的是或否的答案,而 v3 只给一个分数,需要你自己定阈值。实际项目里两者混用也很常见,登录用 v3 静默打分,超过阈值放行,低于阈值再弹出 v2 让用户主动验证。

还有一点需要注意,v3 的分数不是实时的绝对判定,Google 官方建议先在后台观察一段时间真实流量的分数分布,再确定阈值,一般 0.5 是初始参考值。盲目把阈值设得太高会导致大量真实用户被误拦,这是很多团队上线后踩的第一个坑。

二、在 Vue 3 中封装动态脚本加载

官方文档给出的接入方式是在页面里直接插入一个 <script> 标签,这对传统多页应用没问题,但 Vue 3 通常是单页应用,路由切换时组件会反复挂载卸载,如果每次挂载都往 head 里塞一个 script 标签,会导致脚本重复加载,甚至出现 grecaptcha 对象未就绪的竞态问题。正确做法是封装一个全局的脚本加载器,利用 Promise 保证只加载一次。

/**
 * recaptcha-loader.js
 * 动态加载 reCAPTCHA 脚本,全局只加载一次
 */
let loadPromise = null;

export function loadRecaptcha(siteKey) {
  if (window.grecaptcha) {
    // 已经加载过,直接返回就绪的实例
    return Promise.resolve(window.grecaptcha);
  }
  if (loadPromise) {
    // 正在加载中,复用同一个 Promise 避免重复注入
    return loadPromise;
  }
  loadPromise = new Promise((resolve, reject) => {
    // 渲染回调,脚本加载完成后由 Google 的代码调用
    window.onRecaptchaLoaded = () => resolve(window.grecaptcha);
    const script = document.createElement('script');
    script.src =
      'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=' +
      siteKey;
    script.async = true;
    script.defer = true;
    script.onerror = () => reject(new Error('reCAPTCHA 脚本加载失败'));
    document.head.appendChild(script);
  });
  return loadPromise;
}

这段代码的核心在于用模块级的 loadPromise 变量做单例控制。第一次调用时创建 script 标签并返回 Promise,后续调用无论是哪个组件发起,拿到的都是同一个 Promise 实例,天然规避了重复加载。这里还有一个容易忽略的细节:URL 中的 onload 参数指定的回调函数必须挂载在 window 上,因为 Google 的脚本加载完成后是在全局作用域里查找这个函数名,如果你把它写在组件闭包里是找不到的。

对于 v3 场景,拿到 grecaptcha 对象后就可以直接执行了。可以进一步封装一个获取 token 的组合式函数:

/**
 * useRecaptcha.js
 * Vue 3 组合式函数,用于 reCAPTCHA v3 获取 token
 */
import { ref } from 'vue';
import { loadRecaptcha } from './recaptcha-loader';

export function useRecaptcha(siteKey) {
  const loading = ref(false);

  async function execute(action) {
    loading.value = true;
    try {
      const grecaptcha = await loadRecaptcha(siteKey);
      // v3 的 execute 需要传 action 用于后台区分业务场景
      const token = await grecaptcha.execute(siteKey, { action });
      return token;
    } finally {
      loading.value = false;
    }
  }

  return { loading, execute };
}

注意 v3 的 token 有效期只有两分钟左右,而且是一次性的,所以千万不要在页面加载时就提前获取 token 存起来,正确姿势是在用户提交表单的那一刻才调用 execute,拿到 token 立刻随表单一起发给后端。token 里已经包含了 action 信息,后端校验时会返回这个 action,前端和后端可以互相核对防止 token 被跨场景滥用。

三、组件化封装 v2 勾选框与后端校验联调

如果选的是 v2,情况稍微复杂一点,因为勾选框需要渲染到页面的真实 DOM 节点上。在 Vue 3 中可以封装成一个组件,用 <div> 作为渲染容器,在 onMounted 里执行渲染,在 onBeforeUnmount 里做好清理。

<template>
  <div ref="containerRef" class="recaptcha-box"></div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { loadRecaptcha } from './recaptcha-loader';

const props = defineProps({
  siteKey: { type: String, required: true }
});
const emit = defineEmits(['verified']);

const containerRef = ref(null);
let widgetId = null;

onMounted(async () => {
  const grecaptcha = await loadRecaptcha(props.siteKey);
  widgetId = grecaptcha.render(containerRef.value, {
    sitekey: props.siteKey,
    callback: (token) => emit('verified', token),
    // token 过期后的回调,需要提示用户重新勾选
    'expired-callback': () => emit('verified', '')
  });
});

onBeforeUnmount(() => {
  // 组件卸载时只移除渲染的 widget,不销毁全局脚本
  if (widgetId !== null && window.grecaptcha) {
    window.grecaptcha.reset(widgetId);
  }
});
</script>

这里有个关键细节值得展开:卸载组件时调用 reset 而不是尝试销毁脚本。reCAPTCHA 的全局脚本一旦加载就没有官方提供的卸载方法,强行移除 script 标签也不会清理内部状态,反而可能引发报错。所以工程上的处理方式是保留脚本,只重置 widget 状态,下次组件挂载时重新 render 即可,这也是前面做单例加载的原因之一。

前端拿到 token 只是完成了一半,真正的验证必须放在后端。前端的一切数据都是不可信的,token 必须由后端拿着私钥去 Google 的接口核验,请求地址是 https://www.google.com/recaptcha/api/siteverify,把 secret、token 和用户的 IP 传过去,Google 会返回一个 JSON,其中 success 字段表示是否通过,v3 还会附带 scoreaction 字段。后端要同时校验 action 是否与业务场景匹配,不能只看 success 就放行。以 Node.js 为例:

const axios = require('axios');

async function verifyRecaptcha(token, expectedAction) {
  const res = await axios.post(
    'https://www.google.com/recaptcha/api/siteverify',
    null,
    {
      params: {
        secret: process.env.RECAPTCHA_SECRET_KEY,
        response: token
      }
    }
  );
  const data = res.data;
  if (!data.success) return { pass: false, reason: 'verify failed' };
  // v3 必须核对 action,防止 token 被跨接口使用
  if (expectedAction && data.action !== expectedAction) {
    return { pass: false, reason: 'action mismatch' };
  }
  if (typeof data.score === 'number' && data.score < 0.5) {
    return { pass: false, reason: 'score too low' };
  }
  return { pass: true, score: data.score };
}

四、生产环境的几个注意事项

首先是域名白名单。reCAPTCHA 的密钥是在 Google 后台绑定域名的,本地开发用 localhost 没问题,但上线前一定要记得把正式域名加进去,否则线上会出现报错。其次是国内访问 Google 接口可能存在网络问题,如果你的用户群体主要在国内,需要评估可用性,必要时考虑 www.recaptcha.net 这个替代域名,把脚本地址里的 www.google.com 替换掉即可,接口是兼容的。

其次建议对验证结果做好降级策略。reCAPTCHA 本质上是依赖第三方服务的,Google 接口偶尔超时或不可用是客观存在的,如果后端 siteverify 请求失败就直接拒绝用户,体验会很差。常见的做法是把验证失败分成两类:明确判定为机器人的拒绝,网络或服务异常的放行并记录日志,后续通过风控手段二次排查。另外前端脚本加载失败时也要有兜底 UI,比如给用户提供一个提示,而不是表单提交按钮永远转圈。

最后是 TypeScript 支持。grecaptcha 的类型声明需要自己补充,可以在项目里声明一个全局类型文件,给 window.grecaptcha 定义 renderexecute 的签名,这样封装的组件在 VSCode 里就能获得完整的智能提示。这些小的工程化投入看似不起眼,但能明显降低后续维护成本,让团队里其他人用这个组件时不需要去翻 Google 的文档。

Vue 3reCAPTCHA人机验证修改时间:2026-09-11 06:00:40

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260911/54502.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。