用驗(yàn)證器實(shí)戰(zhàn)與源碼解析)
開(kāi)發(fā)工具后端【免費(fèi)下載鏈接】firebase-js-sdkFirebase Javascript SDK項(xiàng)目地址https://gitcode.com/gh_mirrors/fi/firebase-js-sdk點(diǎn)擊查看免費(fèi)下載RecaptchaVerifier是 Firebase JS SDKfirebase-js-sdk中基于 Google reCAPTCHA 的應(yīng)用驗(yàn)證器實(shí)現(xiàn)是手機(jī)號(hào)登錄、手機(jī)號(hào)注冊(cè)等需要人機(jī)校驗(yàn)場(chǎng)景的核心組件。本文以 API 參考文檔 auth.recaptchaverifier.md 為主體結(jié)合packages/auth包源碼完整講解它的構(gòu)造函數(shù)參數(shù)、三個(gè)核心方法render/verify/clear的用法、與signInWithPhoneNumber的配合方式以及底層運(yùn)行原理幫助你在實(shí)際項(xiàng)目中正確接入并避開(kāi)常見(jiàn)陷阱。概述RecaptchaVerifier 是什么RecaptchaVerifier是一個(gè)基于 reCAPTCHA 的應(yīng)用驗(yàn)證器Application Verifier它實(shí)現(xiàn)了 Firebase Auth 內(nèi)部定義的ApplicationVerifierInternal接口export declare class RecaptchaVerifier implements ApplicationVerifierInternal在 Firebase Auth 中凡是需要證明操作來(lái)自真實(shí)用戶的敏感操作典型的如手機(jī)號(hào)登錄/注冊(cè)都必須傳入一個(gè)實(shí)現(xiàn)ApplicationVerifier接口的對(duì)象。從源碼可見(jiàn)ApplicationVerifier接口只要求兩個(gè)成員packages/auth/src/model/public_types.tstype驗(yàn)證器類型標(biāo)識(shí)用于區(qū)分驗(yàn)證方式verify()執(zhí)行驗(yàn)證過(guò)程并返回一個(gè)可用于證明請(qǐng)求有效性的 token。注釋中明確指出目前RecaptchaVerifier是該接口的唯一實(shí)現(xiàn)。因此它是 Firebase Auth 手機(jī)號(hào)驗(yàn)證流程中不可繞過(guò)的關(guān)鍵組件。需要特別強(qiáng)調(diào)的是文檔中的一條重要限制RecaptchaVerifierdoes not work in a Node.js environment.即RecaptchaVerifier不能在 Node.js 環(huán)境下工作它只面向?yàn)g覽器Web環(huán)境。原因是它依賴document、window等瀏覽器全局對(duì)象以及動(dòng)態(tài)加載外部腳本這一點(diǎn)在源碼構(gòu)造函數(shù)與init()方法中都有明確校驗(yàn)見(jiàn)下文運(yùn)行限制一節(jié)。構(gòu)造函數(shù)三個(gè)參數(shù)的完整語(yǔ)義constructor(authExtern: Auth, containerOrId: HTMLElement | string, parameters?: RecaptchaParameters);參數(shù)一authExternAuth 實(shí)例對(duì)應(yīng)你的 Firebase Auth 實(shí)例即getAuth()的返回值。它用于校驗(yàn)、API 請(qǐng)求以及讀取languageCode驗(yàn)證碼控件語(yǔ)言會(huì)跟隨 Auth 實(shí)例的語(yǔ)言設(shè)置。參數(shù)二containerOrId容器reCAPTCHA 掛載的容器可以是DOM 元素HTMLElement也可以是元素的 id 字符串。它的語(yǔ)義隨驗(yàn)證碼的可見(jiàn)性不同而不同源碼構(gòu)造函數(shù)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);三條關(guān)鍵規(guī)則若傳字符串它必須對(duì)應(yīng)一個(gè)已存在于 DOM 中的元素 id初始化時(shí)元素必須已在頁(yè)面上若未找到會(huì)拋出argument-error對(duì)于可見(jiàn)的 reCAPTCHA非 invisible容器元素必須是空的。源碼validateStartingState()中明確斷言_assert( this.isInvisible || !this.container.hasChildNodes(), this.auth, AuthErrorCode.ARGUMENT_ERROR );如果容器里已有子節(jié)點(diǎn)會(huì)直接報(bào)錯(cuò)因此在渲染可見(jiàn)驗(yàn)證碼前要確保容器內(nèi)沒(méi)有任何內(nèi)容 3. 對(duì)于 invisible 驗(yàn)證碼容器主要用于承載隱藏的 iframe規(guī)則相對(duì)寬松。參數(shù)三parameters可選 reCAPTCHA 參數(shù)可選的 reCAPTCHA 渲染參數(shù)類型為 RecaptchaParameters。該接口在源碼中是一個(gè)開(kāi)放索引簽名packages/auth/src/model/public_types.ts即任意字符串鍵都可以傳入export interface RecaptchaParameters { [key: string]: any; }文檔明確說(shuō)明除sitekey外的所有參數(shù)均被接受。sitekey由 Firebase Auth 后端為每個(gè)項(xiàng)目預(yù)先分配SDK 會(huì)在渲染時(shí)自動(dòng)配置用戶不要也不能自行傳入——若在構(gòu)造時(shí)傳了sitekey源碼會(huì)在validateStartingState()中直接拋出參數(shù)錯(cuò)誤recaptcha_verifier.ts。如果你不傳parametersSDK 會(huì)使用如下默認(rèn)值源碼常量 recaptcha_verifier.tsconst DEFAULT_PARAMS: RecaptchaParameters { theme: light, type: image };常用參數(shù)均以 reCAPTCHA 官方文檔為準(zhǔn)參數(shù)說(shuō)明size設(shè)為invisible即啟用 invisible隱形reCAPTCHA不設(shè)置則為可見(jiàn)的復(fù)選框樣式theme主題light/dark默認(rèn)lighttype挑戰(zhàn)類型image/audio默認(rèn)imagecallbacktoken 生成后的回調(diào)可以是函數(shù)也可以是全局函數(shù)名字符串源碼makeTokenCallback兩種都支持expired-callbacktoken 過(guò)期時(shí)回調(diào)SDK 會(huì)忽略空 token 過(guò)期事件type 屬性驗(yàn)證器類型標(biāo)識(shí)readonly type recaptcha;type是只讀屬性固定值為recaptcha源碼常量RECAPTCHA_VERIFIER_TYPErecaptcha_verifier.ts。它用于讓 Firebase Auth 后端識(shí)別本次驗(yàn)證使用的驗(yàn)證器類型實(shí)現(xiàn)ApplicationVerifier接口的協(xié)議要求。render()渲染驗(yàn)證碼控件render(): Promisenumber;在頁(yè)面上渲染 reCAPTCHA 控件返回一個(gè)resolve 為 reCAPTCHA widget ID 的 Promise。render()的源碼實(shí)現(xiàn)值得注意recaptcha_verifier.ts若實(shí)例已銷毀destroyed直接返回一個(gè) rejected Promise若渲染正在進(jìn)行renderPromise已存在返回同一個(gè) Promise避免重復(fù)渲染首次調(diào)用時(shí)觸發(fā)內(nèi)部渲染流程失敗后會(huì)將renderPromise重置為null允許后續(xù)重試。底層渲染流程makeRenderPromise/initrecaptcha_verifier.ts大致為校驗(yàn)當(dāng)前環(huán)境必須為http/https且不是 Web Worker等待 DOM ready動(dòng)態(tài)加載 reCAPTCHA v2 腳本加載細(xì)節(jié)見(jiàn)下文底層原理調(diào)用后端接口getRecaptchaParams獲取項(xiàng)目對(duì)應(yīng)的siteKey若是可見(jiàn)驗(yàn)證碼SDK 會(huì)在容器內(nèi)再創(chuàng)建一個(gè)空的div作為真正的渲染掛載點(diǎn)guaranteedEmpty確保控件渲染不會(huì)受容器原內(nèi)容影響調(diào)用grecaptcha.render(container, parameters)得到 widget ID。verify()等待用戶完成驗(yàn)證并獲取 tokenverify(): Promisestring;等待用戶完成 reCAPTCHA 挑戰(zhàn)并 resolve 出 reCAPTCHA token一個(gè)字符串。這是ApplicationVerifier接口的核心契約也是傳給 Firebase 后端證明操作來(lái)自真實(shí)用戶的憑據(jù)。源碼實(shí)現(xiàn)recaptcha_verifier.ts的邏輯很精巧async verify(): Promisestring { this.assertNotDestroyed(); const id await this.render(); const recaptcha this.getAssertedRecaptcha(); const response recaptcha.getResponse(id); if (response) { return response; // 已有 token直接返回 } return new Promisestring(resolve { const tokenChange (token: string): void { if (!token) { return; // 忽略 token 過(guò)期事件空 token } this.tokenChangeListeners.delete(tokenChange); resolve(token); }; this.tokenChangeListeners.add(tokenChange); if (this.isInvisible) { recaptcha.execute(id); // invisible 模式需要主動(dòng)觸發(fā)執(zhí)行 } }); }要點(diǎn)如果用戶此前已經(jīng)完成過(guò)驗(yàn)證getResponse有值verify()直接復(fù)用該 token不會(huì)再次彈出驗(yàn)證碼否則它注冊(cè)一個(gè)內(nèi)部 token 監(jiān)聽(tīng)器等待驗(yàn)證完成空 token過(guò)期事件會(huì)被忽略繼續(xù)等待下一次成功驗(yàn)證invisible 模式下會(huì)自動(dòng)調(diào)用grecaptcha.execute(id)主動(dòng)觸發(fā)隱形驗(yàn)證無(wú)需用戶點(diǎn)擊可見(jiàn)模式下則等待用戶勾選/完成挑戰(zhàn)后由回調(diào)驅(qū)動(dòng) resolve。clear()清理控件并銷毀實(shí)例clear(): void;清除頁(yè)面上的 reCAPTCHA 控件并銷毀當(dāng)前實(shí)例。源碼實(shí)現(xiàn)recaptcha_verifier.tsclear(): void { this.assertNotDestroyed(); this.destroyed true; this._recaptchaLoader.clearedOneInstance(); if (!this.isInvisible) { this.container.childNodes.forEach(node { this.container.removeChild(node); }); } }將實(shí)例標(biāo)記為destroyed此后調(diào)用render/verify/clear都會(huì)拋出internal-error通知 loader 釋放一個(gè)實(shí)例計(jì)數(shù)對(duì)應(yīng)ReCaptchaLoaderImpl的counter影響后續(xù)腳本復(fù)用策略對(duì)于可見(jiàn)驗(yàn)證碼清空容器內(nèi)的所有子節(jié)點(diǎn)即移除渲染出的控件invisible 模式由于控件承載在 iframe 中不做容器清空。實(shí)戰(zhàn)與手機(jī)號(hào)登錄signInWithPhoneNumber配合RecaptchaVerifier最典型的應(yīng)用場(chǎng)景是手機(jī)號(hào)登錄。官方類型注釋給出了完整示例packages/auth/src/model/public_types.tsconst recaptchaVerifier new RecaptchaVerifier( auth, recaptcha-container, // 容器 id或直接傳 DOM 元素 { size: invisible // 可選使用隱形驗(yàn)證碼 } ); // 觸發(fā)手機(jī)號(hào)驗(yàn)證碼發(fā)送RecaptchaVerifier 會(huì)自動(dòng)完成人機(jī)驗(yàn)證 const confirmationResult await signInWithPhoneNumber( auth, phoneNumber, recaptchaVerifier ); // 用戶輸入短信驗(yàn)證碼后確認(rèn)登錄 const userCredential await confirmationResult.confirm(verificationCode);完整落地步驟在頁(yè)面 HTML 中放置容器可見(jiàn)模式必須為空div idrecaptcha-container/div創(chuàng)建驗(yàn)證器并渲染import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from firebase/auth; const auth getAuth(); const appVerifier new RecaptchaVerifier(auth, recaptcha-container); await appVerifier.render(); // 立即渲染控件也可省略verify 時(shí)自動(dòng)渲染調(diào)用signInWithPhoneNumberSDK 內(nèi)部會(huì)自動(dòng)調(diào)用verify()拿到 token 并隨請(qǐng)求發(fā)送給后端確認(rèn)驗(yàn)證碼confirmationResult.confirm(code)完成登錄、綁定或重新認(rèn)證。集成測(cè)試可以佐證這一調(diào)用關(guān)系packages/auth/test/integration/flows/phone.test.ts中正是先new RecaptchaVerifier(auth, fakeRecaptchaContainer)再將其傳入signInWithPhoneNumberphone.test.ts。使用完例如單頁(yè)應(yīng)用切換頁(yè)面時(shí)應(yīng)調(diào)用appVerifier.clear()釋放資源避免驗(yàn)證碼控件殘留。底層原理reCAPTCHA 腳本加載與 siteKey 獲取腳本動(dòng)態(tài)加載與超時(shí)控制RecaptchaVerifier并不會(huì)要求你在頁(yè)面手動(dòng)引入 reCAPTCHA 腳本而是通過(guò)ReCaptchaLoaderImpl動(dòng)態(tài)加載packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts加載地址為 reCAPTCHA v2 腳本并附加onload回調(diào)名、render: explicit顯式渲染模式以及hl語(yǔ)言參數(shù)取自auth.languageCode網(wǎng)絡(luò)超時(shí)窗口為30~60 秒new Delay(30000, 60000)超時(shí)拋network-request-failed若window.grecaptcha.render已存在比如 App Check 等其他模塊已加載過(guò) reCAPTCHA會(huì)盡量復(fù)用而不重復(fù)加載避免破壞頁(yè)面上已渲染的驗(yàn)證碼。siteKey 由后端下發(fā)你無(wú)需也不能提供 siteKey。在init()中 SDK 會(huì)調(diào)用認(rèn)證 API 的GET_RECAPTCHA_PARAM端點(diǎn)獲取本項(xiàng)目預(yù)分配的recaptchaSiteKeypackages/auth/src/api/authentication/recaptcha.tsexport async function getRecaptchaParams(auth: Auth): Promisestring { return ( ( await _performApiRequestvoid, GetRecaptchaParamResponse( auth, HttpMethod.GET, Endpoint.GET_RECAPTCHA_PARAM ) ).recaptchaSiteKey || ); }拿到 siteKey 后才寫(xiě)入this.parameters.sitekey并完成渲染這就是Firebase Auth 后端為每個(gè)項(xiàng)目配置 reCAPTCHA的具體實(shí)現(xiàn)。測(cè)試模式Mock reCAPTCHA在集成測(cè)試或使用 Auth 模擬器Emulator時(shí)不需要真實(shí)加載 Google 驗(yàn)證碼服務(wù)。SDK 提供了開(kāi)關(guān)auth.settings.appVerificationDisabledForTesting開(kāi)啟該開(kāi)關(guān)后構(gòu)造函數(shù)會(huì)選用MockReCaptchaLoaderImplrecaptcha_verifier.ts直接返回一個(gè)本地MockReCaptcha不會(huì)發(fā)起任何網(wǎng)絡(luò)請(qǐng)求在連接 Auth 模擬器時(shí)該開(kāi)關(guān)會(huì)被自動(dòng)置為truepackages/auth/src/core/auth/emulator.ts這就是為什么使用useEmulator后無(wú)需處理真實(shí)驗(yàn)證碼也能跑通手機(jī)號(hào)登錄流程。運(yùn)行限制與常見(jiàn)錯(cuò)誤排查限制/場(chǎng)景說(shuō)明Node.js 環(huán)境不適用。構(gòu)造時(shí)斷言typeof document ! undefined無(wú) DOM 直接拋operation-not-supported非 http/https 頁(yè)面init()中斷言_isHttpOrHttps()本地file://等協(xié)議無(wú)法工作Web Worker顯式排除init()會(huì)拋internal-error傳入sitekey參數(shù)構(gòu)造時(shí)拋argument-errorsitekey 由后端下發(fā)可見(jiàn)模式容器非空構(gòu)造時(shí)拋argument-error容器必須為空容器 id 不存在構(gòu)造時(shí)拋argument-error銷毀后繼續(xù)使用render/verify/clear拋internal-error網(wǎng)絡(luò)超時(shí)腳本加載 30~60 秒超時(shí)后拋network-request-failed小結(jié)RecaptchaVerifier是 Firebase Auth 在 Web 端進(jìn)行人機(jī)驗(yàn)證的唯一內(nèi)置實(shí)現(xiàn)構(gòu)造時(shí)傳入 Auth 實(shí)例、容器與可選參數(shù)verify()負(fù)責(zé)產(chǎn)出驗(yàn)證 tokenrender()控制控件渲染時(shí)機(jī)clear()負(fù)責(zé)清理配合signInWithPhoneNumber即可完成手機(jī)號(hào)登錄/注冊(cè)的安全校驗(yàn)。理解其sitekey 由后端下發(fā)、腳本動(dòng)態(tài)加載、測(cè)試模式走 Mock這三條實(shí)現(xiàn)主線能幫你在真實(shí)項(xiàng)目中更快定位與解決問(wèn)題。相關(guān)源碼可繼續(xù)查閱 recaptcha_verifier.ts、recaptcha_loader.ts 與集成測(cè)試 phone.test.ts。贊分享開(kāi)發(fā)工具后端【免費(fèi)下載鏈接】firebase-js-sdkFirebase Javascript SDK項(xiàng)目地址https://gitcode.com/gh_mirrors/fi/firebase-js-sdk點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Firebase JS SDK 深度解析PhoneAuthCredential 手機(jī)號(hào)驗(yàn)證碼憑證的機(jī)制與實(shí)戰(zhàn)Firebase JS SDK 深度解析PhoneAuthCredential 手機(jī)號(hào)驗(yàn)證碼憑證的機(jī)制與實(shí)戰(zhàn) 導(dǎo)讀 PhoneAuthCredential 是開(kāi)發(fā)工具后端Firebase JS SDK 核心工具包 firebase/util 完全指南公開(kāi) API、源碼實(shí)現(xiàn)與實(shí)戰(zhàn)用法Firebase JS SDK 核心工具包 firebase/util 完全指南公開(kāi) API、源碼實(shí)現(xiàn)與實(shí)戰(zhàn)用法 導(dǎo)讀 firebase/util 是 F開(kāi)發(fā)工具后端Firebase JS SDK 的 ApplicationVerifier 接口從接口契約到 reCAPTCHA 實(shí)戰(zhàn)Firebase JS SDK 的 ApplicationVerifier 接口從接口契約到 reCAPTCHA 實(shí)戰(zhàn) ApplicationVerifier開(kāi)發(fā)工具后端上一篇5分鐘精通biliTickerBuyB站會(huì)員購(gòu)搶票開(kāi)源工具終極指南下一篇前端路由性能優(yōu)化history庫(kù)高級(jí)應(yīng)用技巧創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考