- 开发工具
- 后端
【免费下载链接】firebase-js-sdk
Firebase Javascript 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);三条关键规则:
- 若传字符串,它必须对应一个已存在于 DOM 中的元素 id(初始化时元素必须已在页面上,若未找到会抛出
argument-error); - 对于可见的 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' |
callback | token 生成后的回调,可以是函数,也可以是全局函数名字符串(源码makeTokenCallback两种都支持) |
expired-callback | token 过期时回调,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):
- 若实例已销毁(
destroyed),直接返回一个 rejected Promise; - 若渲染正在进行(
renderPromise已存在),返回同一个 Promise,避免重复渲染; - 首次调用时触发内部渲染流程,失败后会将
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 模式需要主动触发执行 } }); }要点:
- 如果用户此前已经完成过验证(
getResponse有值),verify()直接复用该 token,不会再次弹出验证码; - 否则它注册一个内部 token 监听器等待验证完成;空 token(过期事件)会被忽略,继续等待下一次成功验证;
- invisible 模式下会自动调用
grecaptcha.execute(id)主动触发隐形验证,无需用户点击; - 可见模式下则等待用户勾选/完成挑战后由回调驱动 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);完整落地步骤:
- 在页面 HTML 中放置容器(可见模式必须为空):
<div id="recaptcha-container"></div>- 创建验证器并渲染:
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from 'firebase/auth'; const auth = getAuth(); const appVerifier = new RecaptchaVerifier(auth, 'recaptcha-container'); await appVerifier.render(); // 立即渲染控件,也可省略(verify 时自动渲染)- 调用
signInWithPhoneNumber,SDK 内部会自动调用verify()拿到 token 并随请求发送给后端; - 确认验证码:
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
相关推荐
Firebase JS SDK 深度解析:PhoneAuthCredential 手机号验证码凭证的机制与实战
Firebase JS SDK 深度解析:PhoneAuthCredential 手机号验证码凭证的机制与实战 导读 PhoneAuthCredential 是
开发工具后端Firebase JS SDK 核心工具包 @firebase/util 完全指南:公开 API、源码实现与实战用法
Firebase JS SDK 核心工具包 @firebase/util 完全指南:公开 API、源码实现与实战用法 导读 @firebase/util 是 F
开发工具后端Firebase JS SDK 的 ApplicationVerifier 接口:从接口契约到 reCAPTCHA 实战
Firebase JS SDK 的 ApplicationVerifier 接口:从接口契约到 reCAPTCHA 实战 ApplicationVerifier
开发工具后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考