ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Firebase JS SDK RecaptchaVerifier 类完全指南:基于 reCAPTCHA 的应用验证器实战与源码解析

Firebase JS SDK RecaptchaVerifier 类完全指南:基于 reCAPTCHA 的应用验证器实战与源码解析
  • 开发工具
  • 后端

【免费下载链接】firebase-js-sdk

Firebase Javascript SDK

项目地址:https://gitcode.com/gh_mirrors/fi/firebase-js-sdk
点击查看免费下载

RecaptchaVerifier是 Firebase JS SDK(firebase-js-sdk)中基于 Google reCAPTCHA 的应用验证器实现,是手机号登录、手机号注册等需要人机校验场景的核心组件。本文以 API 参考文档 auth.recaptchaverifier.md 为主体,结合packages/auth包源码,完整讲解它的构造函数参数、三个核心方法(render/verify/clear)的用法、与signInWithPhoneNumber的配合方式以及底层运行原理,帮助你在实际项目中正确接入并避开常见陷阱。

概述:RecaptchaVerifier 是什么

RecaptchaVerifier是一个基于 reCAPTCHA 的应用验证器(Application Verifier),它实现了 Firebase Auth 内部定义的ApplicationVerifierInternal接口:

export declare class RecaptchaVerifier implements ApplicationVerifierInternal

在 Firebase Auth 中,凡是需要"证明操作来自真实用户"的敏感操作(典型的如手机号登录/注册)都必须传入一个实现ApplicationVerifier接口的对象。从源码可见,ApplicationVerifier接口只要求两个成员(packages/auth/src/model/public_types.ts):

  • type:验证器类型标识,用于区分验证方式;
  • verify():执行验证过程并返回一个可用于证明请求有效性的 token。

注释中明确指出:目前RecaptchaVerifier是该接口的唯一实现。因此它是 Firebase Auth 手机号验证流程中不可绕过的关键组件。

需要特别强调的是文档中的一条重要限制:

RecaptchaVerifierdoes not work in a Node.js environment.

即RecaptchaVerifier不能在 Node.js 环境下工作,它只面向浏览器(Web)环境。原因是它依赖document、window等浏览器全局对象以及动态加载外部脚本,这一点在源码构造函数与init()方法中都有明确校验(见下文"运行限制"一节)。

构造函数:三个参数的完整语义

constructor(authExtern: Auth, containerOrId: HTMLElement | string, parameters?: RecaptchaParameters);

参数一:authExtern(Auth 实例)

对应你的 Firebase Auth 实例,即getAuth()的返回值。它用于校验、API 请求以及读取languageCode(验证码控件语言会跟随 Auth 实例的语言设置)。

参数二:containerOrId(容器)

reCAPTCHA 挂载的容器,可以是DOM 元素(HTMLElement),也可以是元素的 id 字符串。它的语义随验证码的可见性不同而不同,源码构造函数(packages/auth/src/platform_browser/recaptcha/recaptcha_verifier.ts)是这样解析的:

const container = typeof containerOrId === 'string' ? document.getElementById(containerOrId) : containerOrId; _assert(container, this.auth, AuthErrorCode.ARGUMENT_ERROR);

三条关键规则:

  1. 若传字符串,它必须对应一个已存在于 DOM 中的元素 id(初始化时元素必须已在页面上,若未找到会抛出argument-error);
  2. 对于可见的 reCAPTCHA(非 invisible),容器元素必须是空的。源码validateStartingState()中明确断言:
_assert( this.isInvisible || !this.container.hasChildNodes(), this.auth, AuthErrorCode.ARGUMENT_ERROR );

如果容器里已有子节点会直接报错,因此在渲染可见验证码前要确保容器内没有任何内容; 3. 对于 invisible 验证码,容器主要用于承载隐藏的 iframe,规则相对宽松。

参数三:parameters(可选 reCAPTCHA 参数)

可选的 reCAPTCHA 渲染参数,类型为 RecaptchaParameters。该接口在源码中是一个开放索引签名(packages/auth/src/model/public_types.ts),即任意字符串键都可以传入:

export interface RecaptchaParameters { [key: string]: any; }

文档明确说明:除sitekey外的所有参数均被接受。sitekey由 Firebase Auth 后端为每个项目预先分配,SDK 会在渲染时自动配置,用户不要也不能自行传入——若在构造时传了sitekey,源码会在validateStartingState()中直接抛出参数错误(recaptcha_verifier.ts)。

如果你不传parameters,SDK 会使用如下默认值(源码常量 recaptcha_verifier.ts):

const DEFAULT_PARAMS: RecaptchaParameters = { theme: 'light', type: 'image' };

常用参数(均以 reCAPTCHA 官方文档为准):

参数说明
size设为'invisible'即启用 invisible(隐形)reCAPTCHA;不设置则为可见的复选框样式
theme主题,'light'/'dark',默认'light'
type挑战类型,'image'/'audio',默认'image'
callbacktoken 生成后的回调,可以是函数,也可以是全局函数名字符串(源码makeTokenCallback两种都支持)
expired-callbacktoken 过期时回调,SDK 会忽略空 token 过期事件

type 属性:验证器类型标识

readonly type = "recaptcha";

type是只读属性,固定值为'recaptcha'(源码常量RECAPTCHA_VERIFIER_TYPE,recaptcha_verifier.ts)。它用于让 Firebase Auth 后端识别本次验证使用的验证器类型,实现ApplicationVerifier接口的协议要求。

render():渲染验证码控件

render(): Promise<number>;

在页面上渲染 reCAPTCHA 控件,返回一个resolve 为 reCAPTCHA widget ID 的 Promise。

render()的源码实现值得注意(recaptcha_verifier.ts):

  1. 若实例已销毁(destroyed),直接返回一个 rejected Promise;
  2. 若渲染正在进行(renderPromise已存在),返回同一个 Promise,避免重复渲染;
  3. 首次调用时触发内部渲染流程,失败后会将renderPromise重置为null,允许后续重试。

底层渲染流程(makeRenderPromise/init,recaptcha_verifier.ts)大致为:

  • 校验当前环境必须为http/https且不是 Web Worker;
  • 等待 DOM ready;
  • 动态加载 reCAPTCHA v2 脚本(加载细节见下文"底层原理");
  • 调用后端接口getRecaptchaParams获取项目对应的siteKey;
  • 若是可见验证码,SDK 会在容器内再创建一个空的div作为真正的渲染挂载点(guaranteedEmpty),确保控件渲染不会受容器原内容影响;
  • 调用grecaptcha.render(container, parameters)得到 widget ID。

verify():等待用户完成验证并获取 token

verify(): Promise<string>;

等待用户完成 reCAPTCHA 挑战,并 resolve 出 reCAPTCHA token(一个字符串)。这是ApplicationVerifier接口的核心契约,也是传给 Firebase 后端证明"操作来自真实用户"的凭据。

源码实现(recaptcha_verifier.ts)的逻辑很精巧:

async verify(): Promise<string> { this.assertNotDestroyed(); const id = await this.render(); const recaptcha = this.getAssertedRecaptcha(); const response = recaptcha.getResponse(id); if (response) { return response; // 已有 token,直接返回 } return new Promise<string>(resolve => { const tokenChange = (token: string): void => { if (!token) { return; // 忽略 token 过期事件(空 token) } this.tokenChangeListeners.delete(tokenChange); resolve(token); }; this.tokenChangeListeners.add(tokenChange); if (this.isInvisible) { recaptcha.execute(id); // invisible 模式需要主动触发执行 } }); }

要点:

  1. 如果用户此前已经完成过验证(getResponse有值),verify()直接复用该 token,不会再次弹出验证码;
  2. 否则它注册一个内部 token 监听器等待验证完成;空 token(过期事件)会被忽略,继续等待下一次成功验证;
  3. invisible 模式下会自动调用grecaptcha.execute(id)主动触发隐形验证,无需用户点击;
  4. 可见模式下则等待用户勾选/完成挑战后由回调驱动 resolve。

clear():清理控件并销毁实例

clear(): void;

清除页面上的 reCAPTCHA 控件并销毁当前实例。源码实现(recaptcha_verifier.ts):

clear(): void { this.assertNotDestroyed(); this.destroyed = true; this._recaptchaLoader.clearedOneInstance(); if (!this.isInvisible) { this.container.childNodes.forEach(node => { this.container.removeChild(node); }); } }
  • 将实例标记为destroyed,此后调用render/verify/clear都会抛出internal-error;
  • 通知 loader 释放一个实例计数(对应ReCaptchaLoaderImpl的counter,影响后续脚本复用策略);
  • 对于可见验证码,清空容器内的所有子节点(即移除渲染出的控件);invisible 模式由于控件承载在 iframe 中,不做容器清空。

实战:与手机号登录(signInWithPhoneNumber)配合

RecaptchaVerifier最典型的应用场景是手机号登录。官方类型注释给出了完整示例(packages/auth/src/model/public_types.ts):

const recaptchaVerifier = new RecaptchaVerifier( auth, 'recaptcha-container', // 容器 id,或直接传 DOM 元素 { size: 'invisible' // 可选:使用隐形验证码 } ); // 触发手机号验证码发送,RecaptchaVerifier 会自动完成人机验证 const confirmationResult = await signInWithPhoneNumber( auth, phoneNumber, recaptchaVerifier ); // 用户输入短信验证码后确认登录 const userCredential = await confirmationResult.confirm(verificationCode);

完整落地步骤:

  1. 在页面 HTML 中放置容器(可见模式必须为空):
<div id="recaptcha-container"></div>
  1. 创建验证器并渲染:
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from 'firebase/auth'; const auth = getAuth(); const appVerifier = new RecaptchaVerifier(auth, 'recaptcha-container'); await appVerifier.render(); // 立即渲染控件,也可省略(verify 时自动渲染)
  1. 调用signInWithPhoneNumber,SDK 内部会自动调用verify()拿到 token 并随请求发送给后端;
  2. 确认验证码:confirmationResult.confirm(code)完成登录、绑定或重新认证。

集成测试可以佐证这一调用关系:packages/auth/test/integration/flows/phone.test.ts中正是先new RecaptchaVerifier(auth, fakeRecaptchaContainer),再将其传入signInWithPhoneNumber(phone.test.ts)。

使用完(例如单页应用切换页面时)应调用appVerifier.clear()释放资源,避免验证码控件残留。

底层原理:reCAPTCHA 脚本加载与 siteKey 获取

脚本动态加载与超时控制

RecaptchaVerifier并不会要求你在页面手动引入 reCAPTCHA 脚本,而是通过ReCaptchaLoaderImpl动态加载(packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts):

  • 加载地址为 reCAPTCHA v2 脚本,并附加onload回调名、render: 'explicit'(显式渲染模式)以及hl语言参数(取自auth.languageCode);
  • 网络超时窗口为30~60 秒(new Delay(30000, 60000)),超时抛network-request-failed;
  • 若window.grecaptcha.render已存在(比如 App Check 等其他模块已加载过 reCAPTCHA),会尽量复用而不重复加载,避免破坏页面上已渲染的验证码。

siteKey 由后端下发

你无需(也不能)提供 siteKey。在init()中 SDK 会调用认证 API 的GET_RECAPTCHA_PARAM端点获取本项目预分配的recaptchaSiteKey(packages/auth/src/api/authentication/recaptcha.ts):

export async function getRecaptchaParams(auth: Auth): Promise<string> { return ( ( await _performApiRequest<void, GetRecaptchaParamResponse>( auth, HttpMethod.GET, Endpoint.GET_RECAPTCHA_PARAM ) ).recaptchaSiteKey || '' ); }

拿到 siteKey 后才写入this.parameters.sitekey并完成渲染,这就是"Firebase Auth 后端为每个项目配置 reCAPTCHA"的具体实现。

测试模式:Mock reCAPTCHA

在集成测试或使用 Auth 模拟器(Emulator)时,不需要真实加载 Google 验证码服务。SDK 提供了开关auth.settings.appVerificationDisabledForTesting:

  • 开启该开关后,构造函数会选用MockReCaptchaLoaderImpl(recaptcha_verifier.ts),直接返回一个本地MockReCaptcha,不会发起任何网络请求;
  • 在连接 Auth 模拟器时该开关会被自动置为true(packages/auth/src/core/auth/emulator.ts),这就是为什么使用useEmulator后无需处理真实验证码也能跑通手机号登录流程。

运行限制与常见错误排查

限制/场景说明
Node.js 环境不适用。构造时断言typeof document !== 'undefined',无 DOM 直接抛operation-not-supported
非 http/https 页面init()中断言_isHttpOrHttps(),本地file://等协议无法工作
Web Worker显式排除,init()会抛internal-error
传入sitekey参数构造时抛argument-error(sitekey 由后端下发)
可见模式容器非空构造时抛argument-error,容器必须为空
容器 id 不存在构造时抛argument-error
销毁后继续使用render/verify/clear抛internal-error
网络超时脚本加载 30~60 秒超时后抛network-request-failed

小结

RecaptchaVerifier是 Firebase Auth 在 Web 端进行人机验证的唯一内置实现:构造时传入 Auth 实例、容器与可选参数,verify()负责产出验证 token,render()控制控件渲染时机,clear()负责清理;配合signInWithPhoneNumber即可完成手机号登录/注册的安全校验。理解其"sitekey 由后端下发、脚本动态加载、测试模式走 Mock"这三条实现主线,能帮你在真实项目中更快定位与解决问题。相关源码可继续查阅 recaptcha_verifier.ts、recaptcha_loader.ts 与集成测试 phone.test.ts。

  • 开发工具
  • 后端

【免费下载链接】firebase-js-sdk

Firebase Javascript SDK

项目地址:https://gitcode.com/gh_mirrors/fi/firebase-js-sdk
点击查看免费下载
上一篇:5分钟精通biliTickerBuy:B站会员购抢票开源工具终极指南
下一篇:前端路由性能优化:history库高级应用技巧

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进