为什么绝不应该在客户端代码中嵌入Gemini API密钥(以及Firebase AI逻辑是如何解决这个问题的)
生成式人工智能的快速发展促使成千上万的网页开发者在他们的应用程序中添加智能功能。 人们的第一反应通常是从浏览器直接调用Gemini API的SDK。然而,这种做法存在严重的安全风险:会将你的API密钥暴露给外界。 在本文中,你将了解到为什么将原始的Gemini API密钥提供给客户端是危险的,Firebase AI Logic的代理架构是如何解决这一问题的,以及Firebase App Check又是如何弥补单独使用代理所无法解决的问题。 阅读完本文后,你将能够搭建出一个可正常使用的生产环境配置:一个受到保护的AI Logic客户端、一个配置正确的App Check流程(其中包含调试令牌),以
生成式人工智能的快速发展促使成千上万的网页开发者在他们的应用程序中添加智能功能。
人们的第一反应通常是从浏览器直接调用Gemini API的SDK。然而,这种做法存在严重的安全风险:会将你的API密钥暴露给外界。
在本文中,你将了解到为什么将原始的Gemini API密钥提供给客户端是危险的,Firebase AI Logic的代理架构是如何解决这一问题的,以及Firebase App Check又是如何弥补单独使用代理所无法解决的问题。
阅读完本文后,你将能够搭建出一个可正常使用的生产环境配置:一个受到保护的AI Logic客户端、一个配置正确的App Check流程(其中包含调试令牌),以及真实的应用场景——比如流式交互、多轮对话功能,还有结构化的JSON输出结果,而不仅仅是一个简单的console.log语句。
目录
先决条件
在开始之前,请确保你具备以下条件:
Node.js v18或更高版本(运行
node --version即可确认)一个Google账户,用于创建Firebase项目(Gemini开发者API支持免费的Spark计划)
对JavaScript、
async/await语法以及ES模块有基本的了解需要一个代码编辑器和终端环境
你不需要事先具备使用Firebase、App Check或Gemini API的经验,因为本指南会从基础开始帮助你掌握这些知识。
客户端API密钥带来的问题
将API密钥嵌入到JavaScript代码包中,或者放在最终会被发送到浏览器的.env文件中,这种做法存在严重的安全漏洞,而且很容易被恶意利用。下面我们来具体看看实际情况是怎样的。
假设你像这样直接在客户端代码中调用Gemini API:
**请不要在通过浏览器发布的应用程序中使用这种方法**
const genAI = new GoogleGenerativeAI("AIzaSyD4-你的真实API密钥");将此方法与任何构建工具结合使用,API密钥就会以纯文本的形式出现在生成的JS文件中。任何人都能在不到一分钟的时间内找到它,而且不需要任何特殊工具:
任何人都可以使用以下命令来检测你的部署包中是否包含Gemini API密钥:
curl -s https://your-app.com/assets/main.js | grep -oE "AIzaSy[A-Za-z0-9_-]{33}"
通过这个命令,就可以从经过压缩的production版本的应用包中提取出Gemini API密钥(如果该密钥确实存在于其中的话)。在浏览器的开发者工具中,这一信息会更加明显:所有发送到generativelanguage.googleapis.com的请求,其查询字符串或请求头中都会直接显示该API密钥。
如果Gemini API密钥通过这种方式被泄露,攻击者将会能够:
耗尽你所有的使用额度
导致你的Cloud账单金额突然大幅增加(因为Gemini API的调用是按令牌计费的,这与固定费用的数据库查询方式不同)
利用你的资源来发起他们自己的请求,从而导致你的Google Cloud项目因被滥用而被暂停服务
从历史上看,解决这个问题的唯一方法就是构建、部署并维护一个自定义的后端服务器(使用Node.js、Python、Go等语言),让这个服务器充当你的应用与Gemini API之间的代理服务器,从而确保只有这一串API密钥被妥善保护。然而,对于本来应该是一个简单功能的实现来说,这样做实际上意味着需要投入大量的资源来建设基础设施。
步骤1 – Firebase AI Logic的代理架构工作原理
Firebase AI Logic为你提供了这个代理服务,而你无需自己动手构建或托管它。你仍然需要编写客户端代码,但API密钥始终不会离开Google的基础设施。
[Web浏览器] ──(经过身份验证的请求)──> [Firebase AI Logic代理服务器] ──(在服务器端插入API密钥)──>> [Gemini API]
你的Gemini API密钥会被安全地存储在你的Firebase项目中。客户端SDK会向代理服务器发送请求,代理服务器会在请求中插入API密钥,然后将其转发给你选择的“Gemini API”提供者。因此,这个API密钥永远不会出现在你的JS文件中,也不会出现在任何网络请求数据中,浏览器也无法检测到它。
Firebase AI Logic支持两种API提供者,具体选择哪种提供者在设置服务时就可以在控制台中进行配置:
| Gemini开发者API | Agent Platform Gemini API(原名Vertex AI) | |
|---|---|---|
| 计费方案 | 免费版的Spark计划即可使用 | 需要Blaze按需付费计划 |
| 适用场景 | 适合快速入门、进行原型开发,以及大多数Web/移动应用 | 适用于有数据驻留要求,或者已经使用Google Cloud/Vertex AI的团队 |
| 区域控制 | 区域选择范围有限 | 可以根据具体模型需求选择特定的地区(如us、eu)来访问模型 |
| 设置难度 | 设置非常简单,无需进行任何计费操作 | 需要关联一个Cloud Billing账户 |
对于大多数应用来说,建议先使用Gemini开发者API,本教程也是按照这个方式来操作的。以后如果需要更换API提供者,只需要修改配置即可,无需重新编写代码,因为两种提供者都是通过相同的getGenerativeModel()接口来进行交互的。
步骤2 – Firebase应用检查的实际作用
代理服务器会隐藏这些配置信息。但它本身并不能阻止任何人调用该代理服务器。你的Firebase配置对象(如apiKey、projectId等)并非保密信息,而是公开可见的;按照设计,这些配置会出现在每个已部署的应用程序中。如果没有额外的安全措施,机器人就可以复制这些配置信息,使用你的项目信息初始化自己的Firebase应用,然后直接调用你的AI逻辑代理服务器,从而让你的Gemini服务产生费用,而实际上并没有任何真正的用户参与其中。
而这正是Firebase应用检查的作用所在。了解它的具体功能非常重要,因为人们很容易将其与身份验证混淆。
应用检查是一种认证机制,而非身份验证。Firebase身份验证用于回答“这个用户是谁?”这个问题;而应用检查则用来判断“这个请求是否来自我应用程序的合法版本,而不是脚本、机器人或其他使用我的配置信息的应用程序?”你可以也应该将这两种机制结合使用,但正是应用检查在请求中完全缺乏用户相关信息的情况下,也能保护你免受滥用。
其具体工作流程如下:
当你的应用程序初始化应用检查功能时,SDK会向配置好的提供者发送一个认证请求。在网页环境中,这个提供者通常是reCAPTCHA Enterprise;它会进行一系列隐蔽的风险评估(比如检测鼠标移动、浏览器特征以及网络信号),然后返回一个令牌,证明“这是一个合法的浏览器会话”。
你的应用程序的SDK会将这个reCAPTCHA令牌发送到Firebase的应用检查后端服务,后者会将其转换为一个Firebase应用检查令牌——这是一种有效期较短、经过签名的JWT令牌。
这个应用检查令牌会被保存在本地缓存中,并会在你的应用程序后续向Firebase AI Logic或其他集成有应用检查功能的服务发送请求时自动附加到这些请求中。
在AI逻辑代理服务器将请求转发给Gemini之前,它会先验证这个应用检查令牌的签名是否有效。如果令牌无效,请求就不会被传递给相应的服务。
对于生成式AI来说,这一点尤为重要,因为其成本结构与典型的CRUD后端不同:阻止一次Firestore读操作并不会产生任何费用;而如果阻止了一次Gemini请求,而没有采取相应的措施,每次请求都可能会让你付出实际的资金代价。而在大规模应用中,脚本化的滥用行为甚至可能在几小时内就耗尽你的月度预算。应用检查正是确保只有你的应用程序才能触发这些费用的机制。
注意:谷歌已经宣布,从2026年11月2日起,Firebase AI Logic将强制要求使用应用检查功能。实际上,早在2026年7月,Firebase控制台中的引导式设置流程就已经能够为新启用的AI逻辑集成自动启用应用检查功能了。因此在规定日期之后发送的任何未经验证的请求都会被直接拒绝,所以现在就配置这个功能总比以后手忙脚乱要好得多。
步骤3 – 设置您的Firebase项目
访问Firebase控制台,创建一个新的项目(或打开一个已存在的项目)。
在左侧侧边栏中,依次选择构建、AI逻辑,然后点击开始使用。选择Gemini开发者API作为提供商,这样就不需要设置计费信息了。
返回到项目设置页面,依次选择常规设置和您的应用。如果还没有注册Web应用,请先进行注册,然后复制Firebase配置对象。在后续步骤中会需要用到它。
我们将把App Check的配置设置留到下一步再进行讲解,因为这部分内容值得单独介绍。
步骤4 – 集成Firebase App Check
为您的应用注册reCAPTCHA Enterprise
在Firebase控制台中,依次选择构建、App Check,选中您的Web应用,然后选择reCAPTCHA Enterprise作为提供商。Firebase会为您的应用生成一个与域名绑定的站点密钥,请将其复制下来,因为在后续代码中需要使用它。
安装SDK
npm install firebase
您只需要安装这个包即可。ReCaptchaEnterpriseProvider已经包含在firebase/app-check模块中,而firebase/app-check又属于同一个firebase包。因此不需要单独安装reCAPTCHA SDK,也不需要手动添加标签。只要调用initializeAppCheck()方法,Firebase就会自动加载reCAPTCHA Enterprise的相关脚本。
在本地开发环境中使用调试令牌初始化App Check
reCAPTCHA Enterprise在localhost环境下运行时可能会出现不可靠的情况,因此在编写正式生产代码之前,请先设置App Check的调试提供商。这样就可以在本地进行开发,而不会遇到错误拒绝的情况:
// app-check-setup.js
import { initializeApp } from "firebase/app";
import { initializeAppCheck, ReCaptchaEnterpriseProvider } from "firebase/app-check";
const firebaseConfig = {
apiKey: "AIzaSy...",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project",
storageBucket: "your-project.firebasestorage.app",
messagingSenderId: "123456789",
appId: "1:1234:web:abcd",
};
const app = initializeApp(firebaseConfig);
// 仅在本地/开发环境中启用调试提供商。
// 这行代码会在首次运行时将调试令牌输出到控制台。
if (location.hostname === "localhost") {
self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}
export const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider("YOUR_RECAPTCHA_ENTERPRISE_SITE_KEY"),
isTokenAutoRefreshEnabled: true,
});
export { app };
当你在本地首次运行该功能时,请检查浏览器控制台,是否会出现如下内容:
App Check调试令牌:5f2b1a3c-....-....-.... 在使用此令牌之前,你需要将其添加到Firebase控制台中的应用设置中。
将这个令牌复制到控制台中的App Check → 应用程序 → [你的应用] → 管理调试令牌选项中。从那时起,来自你本地机器的请求将被视为已验证,无需再进行reCAPTCHA验证。切勿将此调试令牌发布到生产环境中。你应该像上面所示那样,通过环境检查来控制它的使用。
验证其是否正常工作
一旦你的应用发出了第一个经过App Check保护的请求(你将在第5步中配置这一功能),请进入控制台中的App Check选项,然后选择APIs。你会看到一个实时统计结果,显示哪些请求已经通过验证,哪些没有。如果所有配置都正确,你的请求将会被标记为“已验证”。
第5步 – 配置Firebase AI逻辑
在App Check功能配置完成后,接下来就需要了解如何实际使用这些模型了。
基本设置:
// ai-client.js
import { getAI, GoogleAIBackend, getGenerativeModel } from "firebase/ai";
import { app } from "./app-check-setup.js";
// 使用LimitedUseAppCheckTokens功能可以生成临时令牌,从而为应用提供额外的安全保护,
// 防止重放攻击。
const ai = getAI(app, {
backend: new GoogleAIBackend(),
useLimitedUseAppCheckTokens: true,
});
export const model = getGenerativeModel(ai, { model: "gemini-3.8-flash" });
注意: Gemini模型的名称和可用性会经常发生变化。gemini-2.0-flash及其Lite版本已于2026年6月1日停止使用,目前推荐使用Gemini 3.x系列模型。
在生产环境中硬编码模型名称之前,请务必先查看支持的模型页面.或者更好的做法是,通过Firebase Remote Config动态加载模型,这样就可以在不发布新版本的情况下更换模型了。
示例1 — 单次请求
import { model } from "./ai-client.js";
async function generateAIText(prompt) {
try {
const result = await model.generateContent(prompt);
const response = result.response;
return response.text();
} catch (error) {
console.error("Firebase AI Logic请求失败:", error);
}
}
generateAIText("用两句话解释API代理的作用。");
示例2 — 在用户界面中实时显示结果
对于长度超过一句的文本,使用实时显示功能可以让用户立即看到响应内容,而无需等待几秒钟。下面是一个实际的DOM示例,而不仅仅是控制台日志。import { model } from "./ai-client.js";
async function streamIntoElement(prompt, targetElement) {
targetElement.textContent = "";
const result = await model.generateContentStream(prompt);
for await (const chunk of result.stream) {
targetElement.textContent += chunk.text();
}
// 流式处理完成后,还可以获取最终的响应结果
const finalResponse = await result.response;
console.log("总共使用了多少个令牌:", finalResponse.usageMetadata?.totalTokenCount);
}
const output = document.querySelector("#ai-output");
streamIntoElement("为这款智能水瓶写一段3句话的产品描述!", output);
示例3——多轮对话
对于聊天机器人的功能来说,你并不希望每次进行对话时都手动跟踪并重新发送整个对话内容。startChat()可以帮你处理这个问题:
import { model } from "./ai-client.js";
const chat = model.startChat({
history: [
{ role: "user", parts: [{ text: "我正在开发一个任务管理应用程序。" }] },
{ role: "model", parts: [{ text: "明白了,你需要哪些帮助呢?」 }] },
],
});
async function sendChatMessage(message) {
const result = await chat.sendMessage(message);
return result.response.text();
}
sendChatMessage("请为看板推荐3个状态标签。");
// 后续的对话会自动将之前的对话内容作为上下文:
sendChatMessage("现在再为这3个状态标签分别推荐对应的颜色代码。");
示例4——结构化的JSON输出
如果你需要将模型的输出结果用于应用程序的逻辑处理中(比如渲染卡片内容或填充表单),那么纯文本形式的输出结果往往难以被正确解析。此时,你可以传递一个responseSchema来确保返回的是格式正确、类型明确的JSON数据:
import { getGenerativeModel, Schema } from "firebase/ai";
import { ai } from "./ai-client.js"; // 假设`ai`也是从ai-client.js中导出的
const taskSchema = Schema.object({
properties: {
title: Schema.string(),
priority: Schema.enumString({ enum: ["low", "medium", "high"] }),
tags: Schema.array({ items: Schema.string() }),
},
});
const structuredModel = getGenerativeModel(ai, {
model: "gemini-3.8-flash",
generationConfig: {
responseMimeType: "application/json",
responseSchema: taskSchema,
},
});
async function extractTaskFromText(text) {
const result = await structuredModel.generateContent(
`从这段文字中提取一个任务: "${text}"`
);
return JSON.parse(result.response.text());
}
extractTaskFromText("需要在周五之前审阅Sarah提交的PR,这件事很紧急");
// → { title: "审阅Sarah提交的PR", priority: "high", tags: ["review"] }
示例5——处理速率限制与临时性错误
在高负载情况下,使用Gemini模型进行请求时很可能会遇到速率限制或临时性故障。通过采用简单的指数退避算法,你可以确保你的应用程序具备良好的韧性,而不会对API造成过大的负担:
import { model } from "./ai-client.js";
async function generateWithRetry(prompt, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const result = await model.generateContent(prompt);
return result.response.text();
} catch (error) {
const isRetryable = error.message?.includes("429") || error.message?.includes("503");
if (!isRetryable || attempt === maxRetries) throw error;
const delayMs = 2 ** attempt * 1000; // 1秒、2秒、4秒……
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}
排查常见问题
问题1:API密钥无效。请提供有效的API密钥。
这通常意味着你在Firebase配置中设置的apiKey与你的项目不匹配,或者所需的API功能尚未被启用。请在控制台的项目设置 → 通用选项中再次核对该值是否正确。
问题2:403错误或请求被默默地阻止,尽管你的代码看起来没有问题
这种情况几乎总是由于App Check的注册信息不匹配造成的。请确认你用于测试的域名与你在reCAPTCHA Enterprise中注册的域名一致,并且确保在控制台的App Check选项下,你的请求状态显示为“已验证”(而不是“未强制执行”或完全缺失)。
问题3:App Check在生产环境中可以正常使用,但在localhost上却会失败
这是预期中的现象,因为reCAPTCHA Enterprise在localhost上无法稳定运行。请确保在你的开发环境中启用了第4步中提到的调试令牌功能,并且已经将生成的调试令牌添加到控制台的App Check选项以及管理调试令牌界面中。如果你更换了测试设备或清除了浏览器缓存,那么调试令牌将会被重新生成,你需要再次进行相应的配置。
问题4:模型出现错误或系统意外关闭
谷歌会定期停止使用较旧的Gemini模型,通常会提前几个月发布公告。例如,gemini-2.0-flash及其Lite版本在2026年6月1日被停用。如果某个原本可以正常运行的请求突然返回404错误,请先查看模型页面上的弃用通知,再判断这是否是你的代码中的问题。
问题5:流式传输在过程中突然停止,且没有出现任何错误
这种情况通常是由于usageMetadata/token-limit的限制导致的,并非网络故障。你可以查看finalResponse.candidates[0].finishReason这个值(在result.response解析完成后可以获取),如果该值为MAX_TOKENS之类的数值,那就说明响应被提前截断了,而不是系统出现了崩溃。
结论
开发具有生成式能力的人工智能功能意味着从原型设计阶段就开始采用成熟的安全措施,而不是在后期才添加这些安全机制。Firebase AI Logic的代理架构能够确保Gemini API密钥完全不会出现在客户端代码中,而Firebase App Check则能保证即使密钥被隐藏,也只有真实的应用程序才能使用你的配额。这两项技术共同解决了最为关键的两个问题:密钥被盗用以及恶意脚本攻击。
接下来还有一些值得探索的内容:
函数调用/工具使用——这样Gemini就可以在响应中调用你自己的应用程序功能。
服务器端提示模板:如果你希望自己的提示信息也完全不出现在客户端代码中,而不仅仅是API密钥,这个选项非常有用。
混合推理机制:在支持该功能的浏览器中,系统会优先使用设备内置的模型进行处理,从而降低简单请求的成本并减少延迟。
利用Firebase Remote Config配置模型名称:这样你就可以在不发布新应用程序版本的情况下,将新的Gemini模型推送给用户。
相关文章
如何连接英国税务海关总局的“数字化纳税”API:初学者指南
如果你为那些需要缴纳英国税款的用户开发软件,那么迟早你会需要与 HMRC 进行沟通。 针对所得税的“数字化纳税”计划于2026年4月6日正式实施,目前自我雇用人以及年收入超过50,000英镑的房东必须遵守这一规定。到2027年4月,这一收入门槛将降至30,000英镑;而到2028年4月,则进一步降低到20,000英镑。因此,在未来两年内,受该规定影响的人群数量将会大幅增加。 实际上,这意味着许多小型企业现在都需要能够将其财务数据发送给HMRC的软件,而有人就需要开发这样的软件。 当你第一次阅读HMRC提供的开发者文档时,会看到一大堆缩写词:MTD、ITSA、OAuth权限范围、防欺诈头部信息以
阅读全文
如何使用Hono和Zod构建类型安全的API
如果你之前曾经开发过 Node.js API,那么你就应该了解这种麻烦:TypeScript 中定义的类型与运行时验证的结果不一致,而 OpenAPI 文档的内容也与这两者都不同。 有时有人会在接口中添加新的字段,但相应的模式文件却从未得到更新。因此,文档内容会一直保持过时的状态,直到有客户端提交错误报告为止。这种情况下,既不会出现编译错误,也不会有测试失败的情况——这三个信息来源就这样出现了分歧。 在本教程中,你将学习如何使用 Hono 和 Zod 将这些问题统一起来。你会学到我在实际开发中使用的那些模式,包括我在维护的开源视频处理工具包 ClipForge 中所采用的方案。这些方法能够确保
阅读全文
OpenTelemetry的工作原理:一份全面的指南
如果你是一名软件开发人员或DevOps工程师,那么你很可能已经听说过OpenTelemetry。在讨论可观测性、监控或分布式系统的调试时,这个术语经常会被提及。 你可能也知道它的基本定义,但了解OpenTelemetry是什么与真正理解它的运作原理其实是两回事。 读完本指南后,你将能够明白OpenTelemetry是如何从端到端工作的——从请求进入你的应用程序的那一刻起,直到这些数据被显示在可观测性后端系统中。你还会了解到追踪信息、时间跨度、上下文传播机制以及数据导出工具是如何共同构成一个完整的处理流程的。 如果你完全不了解OpenTelemetry,也别担心:接下来的部分会帮助你快速掌握相关
阅读全文
为什么你绝不应该在API请求中包含电子邮件地址
在开发环境中,你的注册接口看起来没有任何问题。用户完成注册后,你会将相关数据保存到数据库中,然后调用邮件服务提供商,并返回状态码 201 Created ,这样用户就会收到欢迎邮件。一切似乎都很顺利。 然而,当生产环境中的请求开始涌入时,问题出现了: 邮件发送接口现在需要2秒钟才能完成响应,而不是原本的200毫秒。因此,有些请求会超时失败。而在邮件服务提供商出现故障的情况下,所有注册请求都会返回状态码 500 。技术支持人员很困惑:为什么用户能够创建账户,但却始终收不到确认邮件链接? 其实问题并不出在你的邮件模板上,而在于你选择将邮件发送处理逻辑放在HTTP请求路径中这一决策。 在这篇文章中,
阅读全文