Expo 应用 API 密钥安全:哪些该放服务器
作者:Mehdi Azizi
更新于 ,7分钟
EXPO_PUBLIC_ 变量会被用户读取,密钥必须放服务器;再用两个测试账号验证访问权限。
环境变量不一定是密钥
在 Expo 应用中,以 EXPO_PUBLIC_ 为前缀的变量是给应用代码用的,不能放服务器密钥。Expo 说明,这些值会被打包进编译后的 JavaScript,应用的用户可以读到。所以,把密钥挪到 .env 文件里并不能保护它。来源:Expo 环境变量。
在让 AI“接入 API”之前,先给你交给它的东西分类:服务地址、本来就设计为公开的密钥,还是能执行特权操作的密钥。这个分类决定了代码该在哪里运行。
下表是一份设计参考,不是对你应用的审计。
| 内容 | 放在哪里 | 需要检查什么 |
|---|---|---|
| 后端公开地址 | 应用配置 | 指向正确的环境。 |
| Supabase publishable 密钥,或旧版 anon 密钥 | 权限配置正确时可放在应用中 | 数据规则确实能阻止访问其他用户的账号。 |
| Supabase secret 或 service_role 密钥 | 只放服务器 | 不出现在移动端 bundle 中,也不出现在客户端可访问的日志里。 |
| AI 服务商的密钥 | 只放服务器 | 应用调用你的服务器,由服务器先检查用户和限额,再调用服务商。 |
Supabase 区分了供客户端使用的密钥和供可信环境使用的密钥。公开密钥只用来标识项目,它不能代替数据权限。来源:Supabase API 密钥。
示例:一个虚构的读书笔记应用
假设有个应用,Léa 和 Sami 在里面记录自己的书和私人笔记。这个例子是用来准备测试的,并不是在某个 Devoria 应用上得到的结果。
正常流程看起来没问题:Léa 登录,看到自己的笔记。但这还没回答关键问题:如果 Léa 请求 Sami 的笔记,服务器会拒绝吗?
界面里的过滤,比如“显示当前登录用户的笔记”,只是改善显示。授权还必须在数据层执行。在 Supabase 中,Row Level Security 规则可以根据会话限制可访问的行。Supabase 建议对通过 API 暴露的表启用它,并说明了读取和写入策略。来源:Row Level Security。
在你自己的测试项目里,把每条笔记关联到它的所有者,并分别定义谁可以读取、创建、修改和删除。文件也要检查:保护了数据库中的一行,并不能证明某张图片是私密的。
用两个测试账号执行的验收步骤
准备两个你自己控制的账号,每个账号里放一本不同的书和一条容易辨认的短笔记。在获得授权的测试环境里使用虚构数据。
- 读取自己的笔记。 账号 A 能看到笔记 A。
- 用 A 读取 B 的笔记。 请求不会泄露其私密内容。
- 用 A 修改 B 的笔记。 服务器拒绝,笔记 B 保持不变。
- 用 A 以 B 的名义创建。 服务器拒绝这种归属。
- 无会话读取。 不返回任何私密内容。
- 用 A 打开 B 的私密文件。 文件无法访问。
记录实际结果、测试的版本以及可能的错误。“规则已启用”不是测试结果。资源页中的验收表可以作为保存这些证据的基础。
让付费调用经过你的服务器
比如一个用AI API总结书籍的功能,流程可以是:应用 → 你的服务器 → 服务商 → 你的服务器 → 应用。密钥留在服务器上。但光有这个架构还不够:对服务器的访问也必须受控。
在这个虚构示例中,服务器尤其要验证会话、确认这本书属于该用户、限制重复请求,并在服务商无响应时返回易懂的错误。客户端不能自己决定某个用户享有付费权限。
准备四个测试:未登录用户、其他账号的书、达到限额、服务商不可用。最后一个用来确认应用能退出加载状态,且不丢失用户输入。
你可以让编程助手做什么
下面是一条审查指令,按你的代码仓库调整,不要把你的密钥值贴进去:
列出移动端代码和服务器使用的变量,只显示变量名。找出客户端中存在的特权调用。针对私密数据,描述读取和写入的控制方式,然后提出一套使用两个测试账号的验收步骤。未经确认,不要轮换任何密钥,也不要修改生产环境。
对照相应代码阅读它的回答。助手能发现不一致之处,但它的回答不能代替真正执行测试。用 Claude Code 创建应用这篇指南讲了如何拆分这项工作。
继续开发应用之前
你应该能简单说清三件事:哪些值是公开的,哪些操作经过服务器,哪些测试证明一个账号看不到另一个账号的数据。如果其中有一点还模糊,先理清楚再加新功能。
创建 iPhone 应用页面把这一步放回完整项目中来看。想按顺序推进的话,iOS Kit 提供四个步骤的路线,以及已经包含账号和 AI 功能的 Starter Code。如果不知道该从哪一步继续,免费诊断会帮你理顺项目,并把我的 Apple 防拒审清单发给你。
常见问题
Expo 应用里的 API 密钥应该放在哪里?
放在你的服务器上,绝不放进应用。Supabase 的 secret 或 service_role 密钥,以及 AI 服务商的密钥,都只能留在服务器端。应用调用你的服务器,由服务器先检查用户和限额,再调用服务商。确认这些密钥都不会出现在移动端 bundle 中,也不会出现在客户端可访问的日志里。
.env 文件能保护 Expo 里的 API 密钥吗?
单靠它不行。以 EXPO_PUBLIC_ 为前缀的变量是给应用代码用的,不能放服务器密钥。Expo 说明,这些值会被打包进编译后的 JavaScript,应用的用户可以读到。所以,把密钥挪到 .env 文件里并不能保护它。
公开 API 密钥和私密密钥有什么区别?
公开密钥本来就是要随应用发布的:它标识项目,但不能代替数据权限。私密密钥能执行特权操作,只能在可信环境中使用。在 Supabase 中,publishable 密钥或旧版 anon 密钥在权限配置正确时可以留在应用里。secret 和 service_role 密钥则留在服务器上。
如何在 Expo 应用中调用 AI API 而不暴露密钥?
让调用经过你的服务器:应用调用服务器,服务器调用服务商,再把结果返回给应用。密钥留在服务器上。服务器还要验证会话、限制重复请求,并在服务商无响应时返回易懂的错误。客户端不能自己决定某个用户享有付费权限。
如何确认每个用户只能看到自己的数据?
准备两个你自己控制的测试账号,每个账号里放一条容易辨认的笔记。用账号 A 尝试读取、修改账号 B 的内容,以 B 的名义创建内容,再尝试打开 B 的私密文件。也要做无会话测试:每个请求都应被拒绝或不泄露任何内容。记录实际结果,因为“规则已启用”不是测试结果。
如何让 AI 检查我应用里的密钥?
让它列出移动端代码和服务器的变量,只显示变量名,不要贴你的密钥值。再让它找出客户端中存在的特权调用,并提出一套使用两个测试账号的验收步骤。禁止它在未经确认的情况下轮换密钥或修改生产环境。它的回答不能代替真正执行测试。