如何保障应用中 API 密钥的安全
要保障 API 密钥的安全,请把它们保存为托管平台上的机密或环境变量,只在服务端代码中使用,并且绝不要把它们粘贴到聊天、代码文件、截图或 Git 代码仓库中。为每个密钥只授予它所需的最小权限,在服务商处设置消费上限;如果某个密钥曾经泄露,就轮换它,即创建一个新的并删除旧的,而不是只是把它藏起来。
API 密钥就是服务的密码。你的 OpenAI 密钥可以让别人花你的钱使用 AI;你的 Stripe 私钥可以让别人发起退款;你的邮件服务商密钥可以让别人以你的名义发送邮件。AI 应用构建器很擅长把这些密钥接入应用,却不总是足够小心它们最终会出现在哪里,因此本指南介绍如何保障它们的安全,以及如何进行检查。
公开密钥与私密密钥
并非所有密钥都是机密的。了解二者的区别,可以避免你为错误的密钥恐慌,又忽视了真正需要保护的那个。
| 类型 | 示例 | 能否放在浏览器中? |
|---|---|---|
| 可公开密钥 / 公开密钥 | Stripe 的 pk_… 密钥、限定域名使用的地图密钥、部分分析工具 ID | 可以,这是设计用途 |
| 私密密钥 | Stripe 的 sk_…、OpenAI 和 Anthropic 密钥、邮件服务商密钥、数据库密码 | 绝不可以 |
| 服务或管理员密钥 | 能够绕过托管数据库访问规则的密钥 | 绝不可以,应视为最敏感的密钥 |
如果你不确定某个密钥属于哪一类,请查阅服务商的文档。拿不准时,就把它当作私密密钥处理。
第一步:把密钥保存为机密,而不是写在代码里
密钥应当放在托管平台的环境变量或机密设置中。你的代码在运行时通过名称读取它们(例如 process.env.OPENAI_API_KEY),这样密钥的值就永远不会出现在你的文件中。背景知识请参阅什么是环境变量和机密。
把每一个 API 密钥从代码中移到环境变量里。列出你找到的每个密钥、它原来所在的文件,以及你使用的变量名。不要显示它们的值。
然后检查是否还有遗漏:
搜索整个项目,包括配置文件和任何 .env 文件,查找看起来像 API 密钥或密码的内容。请报告文件和行号,而不是值本身。
第二步:私密密钥只在服务端使用
这是最重要的规则。浏览器接收到的任何内容,访问者都可以读取。页面源代码和 JavaScript 文件在开发者工具中都是可见的。
因此,如果应用需要调用 OpenAI,浏览器应该调用你的服务器,由你的服务器使用密钥调用 OpenAI。密钥永远不会离开服务器。
聊天功能目前直接从浏览器调用 OpenAI。把这个调用移到服务端 API 路由中,该路由从环境变量读取 OPENAI_API_KEY。浏览器只应调用我们的路由。该路由要求用户登录。
在前端框架中,有一个常见陷阱需要留意:带有公开前缀的变量,例如 Vite 中的 VITE_、Next.js 中的 NEXT_PUBLIC_,它们会被有意地打包进浏览器代码。即使私密密钥“保存在环境变量里”,只要带上这些前缀,它也会暴露出去。
第三步:测试是否有泄露
不要想当然地认为已经生效,请检查线上网站:
- 在浏览器中打开已发布的应用。
- 打开开发者工具,然后切换到 Sources(源代码) 或 Network(网络) 标签页。
- 在各个文件中搜索每个私密密钥的前几个字符(例如
sk_或sk-)。 - 检查应用发出的网络请求。如果请求直接从浏览器发送到付费的 AI 服务商,这就是一个危险信号。
如果发现私密密钥,请把它移到服务端(第二步),并轮换它(第六步),因为它已经处于公开状态。
第四步:限制每个密钥的权限
许多服务商允许你限制密钥:
- 有范围或受限的密钥,只能执行你的应用所需的操作。例如,Stripe 支持带有特定权限的受限密钥。
- 域名或 IP 限制,适用于必须公开的密钥,例如地图密钥。
- 为每个应用和每个环境分别使用独立的密钥,这样一次泄露不会暴露所有内容,撤销其中一个也不会影响其他密钥。
- 开发时使用测试密钥,只在已发布的应用中使用正式密钥。
第五步:为损失设置上限
即使是保护得很好的密钥,也可能通过你自己的应用被滥用,例如有人编写脚本,对你的 AI 功能进行数千次调用。
- 在每一个收费的服务商处设置消费上限或提醒。
- 对任何按次收费的功能,要求登录。
- 添加针对每位用户的速率限制。参见什么是速率限制。
为 /api/chat 添加限制:每位用户每小时最多 30 次 AI 请求。达到限制时返回清晰的提示信息。拒绝未登录用户的请求。
第六步:轮换泄露的密钥
如果私密密钥出现在了不该出现的地方(聊天消息、提交记录、截图、支持工单、浏览器代码),请假定它已经被泄露。
- 在服务商处创建一个新密钥。
- 在应用的设置中更新机密。
- 重新部署或重新发布,使线上应用使用新密钥。
- 在服务商处删除旧密钥。
- 检查服务商的用量日志,看看是否有你不认识的活动。
删除聊天消息或 Git 提交是不够的。Git 历史会保留旧版本,而任何公开过的内容可能早已被复制。
常见错误
- 把密钥粘贴进提示词。 它现在就存在于聊天记录中了。请改用机密表单。
- 私密变量带有公开前缀。
VITE_OPENAI_API_KEY会把密钥发给每一位访问者。 - 提交了
.env文件。 在首次推送之前,确保它已被列入.gitignore。 - 所有地方都使用同一个密钥。 测试和正式环境、每个应用,都应使用独立的密钥。
- 没有消费上限。 泄露或被滥用的、没有上限的密钥,可能在你注意到之前就已经产生巨额账单。
- 隐藏而不是轮换。 密钥一旦暴露,只有更换新密钥才能解决问题。
- 截图中包含密钥。 在分享设置页面寻求帮助之前,请裁剪或模糊处理。
检查清单
- 每个密钥都保存为机密或环境变量,而不是写在代码里。
- 没有任何
.env文件或密钥被提交到 Git。 - 私密密钥只用于服务端代码。
- 没有私密变量使用
VITE_或NEXT_PUBLIC_等公开前缀。 - 我已在线上网站的 JavaScript 中搜索了每个密钥的前缀,没有找到任何内容。
- 在服务商支持的情况下,密钥已设置范围或限制。
- 测试和正式密钥是分开的。
- 按次收费的功能需要登录,并设有针对每位用户的限制。
- 每个收费的服务商都设有消费上限或提醒。
- 我知道如何轮换每个密钥,并且任何曾经暴露的密钥都已轮换。
在 Mythex 中保障密钥安全
Mythex 通过两种方式让密钥远离你的代码和聊天:
- 机密请求卡片。 当智能体需要密钥时,它会在聊天中显示一张卡片,你在表单字段中输入值,而不是在消息输入框中输入,该值会保存为项目机密。参见聊天中的机密。
- 项目设置 → 机密,或聊天中的
/secrets,用于由你自己添加或修改密钥。参见环境变量与机密。
为已发布的应用更改机密之后,请重新发布,以便线上应用获取新值。导出的项目不包含机密,因此在你下次运行代码的任何地方,都需要重新添加它们。如果密钥曾经出现在聊天中,请在服务商处轮换它。
关于上线前的其余安全检查,请完整走一遍AI 构建应用安全检查清单。如果你正在添加 AI 功能,如何使用 LLM API会更深入地讲解密钥和成本问题。
常见问题
把 API 密钥放在前端代码里安全吗?
只有当服务商设计该密钥就是用于公开时才安全,例如 Stripe 的可公开密钥,或是限定在你域名下使用的地图密钥。任何能够消费金钱或读取私密数据的密钥,都必须留在服务端,因为发送到浏览器的任何内容都可能被任何人读取。
如果我把 API 密钥粘贴到了聊天或公开代码仓库中,该怎么办?
轮换它:在服务商处创建一个新密钥,更新应用的机密,重新发布,然后删除旧密钥。仅仅删除消息或提交记录是不够的,因为副本可能已经存在。
API 密钥应该存放在哪里?
存放在你的托管平台的环境变量或机密设置中,由服务端代码在运行时读取。它们不应被写入源文件、提交到 Git,或出现在截图中。
如何判断我的 API 密钥是否在网站中暴露了?
打开你的线上网站,在浏览器开发者工具中查看页面源代码和 JavaScript 文件,然后搜索密钥的开头部分。如果找到了,就说明它已暴露,应当将其移到服务端并轮换。