news 2026/9/10 17:14:32

Expo Secrets 管理指南:官方客户端密钥的存储边界与构建期注入机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Expo Secrets 管理指南:官方客户端密钥的存储边界与构建期注入机制

Expo Secrets 管理指南:官方客户端密钥的存储边界与构建期注入机制

【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo

secrets/是 Expo 开源仓库中专供 Expo 团队内部使用的密钥目录,存放用于配置官方 Expo Go 客户端、连接 Expo 自有服务(Firebase、Google Maps、Analytics 等)的 API Key 等数据。本文基于 secrets/README.md 展开,结合仓库源码详解这些密钥的安全边界、Google Cloud Secret Manager 存取工作流,以及它们如何在 Android / iOS 构建期通过模板替换机制注入到官方客户端中——同时说明为什么 Expo 软件本身完全不依赖这些密钥,普通开发者也不需要它们。

一、secrets 目录的定位:仅供 Expo 团队,软件本身不依赖

按照 secrets/README.md 的说明,这个目录只对 Expo 团队有意义。它包含两类数据:

  • 真正属于机密、但暴露后影响可控的凭据;
  • 并非机密、却只应出现在官方发布版本中的数据,例如客户端 API Key——防止它们在开发者自己的构建中被意外带入。

关键事实是:Expo 的软件(SDK、CLI、模板)离开这些密钥依然可以正常工作。这些密钥的唯一用途是配置官方 Expo Go 客户端并连接 Expo 自用的服务。这一点从仓库的构建脚本可以得到印证:所有消费secrets/的逻辑都存在一个公开的"兜底"路径(详见下文"源码级验证"部分),没有解密密钥时构建会静默回退到公开占位值,而不是失败。

从源码结构看,secrets/目录在公开仓库中只有一个 secrets/README.md 文件,真实的keys.json等密钥文件不会出现在仓库中——这与文档中"解密后的密钥被 gitignore、不会被提交"的说明一致。

二、安全红线:纵深防御,严禁存放高危凭据

secrets/README.md对"什么能放进这个目录"给出了非常明确的安全边界:

不要将特别敏感、难以撤销的凭据(例如 Android keystore)放入本仓库或 CI,即使它们已被加密。

这是典型的**纵深防御(defense in depth)**原则。即使这些密钥不幸泄露,也只允许"不会造成重大损害或大量额外工作"的后果被触发;而 Android keystore 这类凭据一旦暴露,意味着应用签名体系被攻破,需要重新签名、更换身份、影响所有已发布版本,代价极高,因此必须排除在仓库之外。

由此可以总结出 Expo 团队选择密钥的取舍标准:

可以放入禁止放入
客户端 API Key(泄露影响可控)Android keystore / 签名凭据
仅用于官方发布版的非机密数据难以撤销的高危凭据
服务配置类 Key即便是加密形式的高危凭据

密钥的权威存储介质是Google Cloud Secret Manager,本地只保留按需同步的副本——这本身也是纵深防御的一环:仓库/CI 中不长期驻留密钥数据,只在使用时解密到本地。

三、密钥存取工作流:解锁与锁定

解锁(./bin/unlock

在仓库根目录运行./bin/unlock,即可从 Google Cloud Secret Manager 拉取并解密密钥到本地机器。

前置条件:

  1. 安装 Google Cloud SDK(brew install google-cloud-sdk);
  2. 通过gcloud auth login完成身份认证;
  3. 设置项目:gcloud config set project exponentjs
  4. 拥有对exponentjs项目、具备services-secrets-accessor权限的访问权——需要时可在 Google Cloud Console 的 IAM/管理员权限申请页面(对应项目exponentjs)提交访问申请。

解锁后的状态:密钥会以明文形式落在本地的secrets/目录中,该目录已被 gitignore,不会被提交到仓库。也就是说,任何团队成员只要拥有相应权限,随时可以本地解锁、参与官方客户端构建,但密钥永远不会进入版本历史。

锁定(./bin/lock

在仓库根目录运行./bin/lock,即可删除本地secrets/目录中的密钥文件,完成"本地不留存敏感数据"的收尾。典型的操作节奏是:需要构建官方客户端时解锁 → 构建完成后立即锁定。

说明:./bin/unlock./bin/lock属于 Expo 团队内部维护的工具脚本,不随公开仓库分发(公开仓库根目录下不存在bin/目录),公开仓库中实际可见的只有 secrets/README.md 这份说明文档。

四、源码级验证:密钥在构建期如何被消费

secrets/README.md只描述了密钥的存取流程,而仓库源码完整展示了这些密钥的消费链路。理解这条链路,才能明白为什么"没有密钥也能构建"。

4.1 统一读取:解密密钥优先,公开占位值兜底

Android 侧的动态宏生成逻辑位于 tools/src/dynamic-macros/generateDynamicMacros.ts。其核心函数getTemplateSubstitutionsFromSecrets的读取顺序是:

  1. 优先读取secrets/keys.json(解密后的真实密钥);
  2. 如果读取失败(例如没有解密密钥),打印提示"You don't have access to decrypted secrets. Falling back to template-files/keys.json.",然后回退到 template-files/keys.json(公开的占位值);
  3. 随后还会尝试读取仓库根目录的private-keys.json,用于在默认密钥之上做覆盖。

iOS 侧的实现是构建阶段脚本 apps/expo-go/ios/Build-Phases/generate-dynamic-macros.sh,其load_keysget_key逻辑与 Android 侧完全对称:secrets/keys.jsontemplate-files/keys.jsonprivate-keys.json覆盖。两个平台共享同一套"解密密钥优先、公开占位兜底、私有覆盖"的键值解析模型。

4.2 公开占位密钥长什么样

template-files/keys.json 是随仓库公开的"默认密钥表",所有真实值被清空或替换为明显的测试占位值,例如:

{ "GOOGLE_TRACKING_ID": "", "GOOGLE_MOBILE_SDK_APP_ID": "1:300000000000:android:0000000000000000", "GOOGLE_PROJECT_NUMBER": "", "GOOGLE_CLIENT_API_KEY": "", "GOOGLE_MAPS_API_KEY": "", "FIREBASE_CLIENT_ID": "test-do-not-use.apps.googleusercontent.com", "FIREBASE_API_KEY": "A00000000000000000000000000000000000000", "FIREBASE_GCM_SENDER_ID": "999999999999", "FIREBASE_BUNDLE_ID": "host.exp.Exponent", "FIREBASE_GOOGLE_APP_ID": "1:999999999999:ios:0000000000000000", "FIREBASE_DATABASE_URL": "https://test-do-not-use.firebaseio.com" }

从中可以看到密钥覆盖的典型服务面:Google Analytics / 移动 SDK(GOOGLE_*)、Google Maps(GOOGLE_MAPS_API_KEY)与 Firebase(FIREBASE_*全套)。解密后的secrets/keys.json拥有完全相同的键结构,只是值为真实配置——这正是模板替换机制能无缝切换两种来源的原因。

4.3 模板替换:从占位符到目标文件的注入

secrets/template-files/的配合方式是:模板文件里写${KEY_NAME}占位符,构建时用密钥表中的值做正则替换,写入目标工程文件

以 Android 为例,template-files/android/google-services.json 中的占位符包括${GOOGLE_PROJECT_ID}${GOOGLE_MOBILE_SDK_APP_ID}${GOOGLE_CLIENT_API_KEY}${CERTIFICATE_HASH}${GOOGLE_TRACKING_ID}等;template-files/android/mobile/AndroidManifest.xml 中则用${GOOGLE_MAPS_API_KEY}注入 Maps Key。

iOS 侧同理,template-files/ios/GoogleService-Info.plist 中声明了${FIREBASE_API_KEY}${FIREBASE_CLIENT_ID}${FIREBASE_GCM_SENDER_ID}${FIREBASE_BUNDLE_ID}${FIREBASE_GOOGLE_APP_ID}等占位符;apps/expo-go/ios/Build-Phases/generate-dynamic-macros.sh 中维护了FIREBASE_*十个键的替换列表,逐个用sed完成替换。

替换的目标位置由路径映射文件声明:

  • template-files/android-paths.json:google-services.jsonapps/expo-go/android/app/google-services.json,以及AndroidManifest.xmlapps/expo-go/android/app/src/main/AndroidManifest.xml(含mobile/quest/变体);
  • template-files/ios-paths.json:GoogleService-Info.plistapps/expo-go/ios/Exponent/Supporting/GoogleService-Info.plist

替换逻辑(tools/src/dynamic-macros/generateDynamicMacros.ts)会对template-files/下的模板做逐键\$\{KEY\}全局替换,并且只有内容发生变化时才写回目标文件;debug/mobileDebug 配置还会额外注入测试权限。这意味着:没有解密密钥时,构建产物中只会出现公开占位值,官方客户端需要真实服务的场景才会受影响,本地开发与社区构建不受影响

4.4 动态宏生成命令

Android 侧可以通过 expotools 命令手动触发整条链路(tools/src/commands/AndroidGenerateDynamicMacros.ts):

et android-generate-dynamic-macros

支持--buildConstantsPath(生成ExponentBuildConstants.java的路径)与--configuration(构建配置,默认取process.env.CONFIGURATIONrelease)两个可选参数;iOS 侧则建议直接使用构建阶段脚本apps/expo-go/ios/Build-Phases/generate-dynamic-macros.sh(expotools 的 iOS 生成路径已被官方标记为 deprecated)。

五、对普通开发者的结论

  • 你不需要这些密钥:Expo 的 SDK、模板与构建工具在无密钥状态下正常工作,回退逻辑(tools/src/dynamic-macros/generateDynamicMacros.ts 与 apps/expo-go/ios/Build-Phases/generate-dynamic-macros.sh)保证构建链路可用。
  • 密钥与官方发布绑定secrets/里的数据只服务于官方 Expo Go 客户端及其自有服务连接,是 Expo 团队内部工作流(Google Cloud Secret Manager 存取 +./bin/unlock/./bin/lock本地同步)的一部分,与公开仓库的日常使用解耦。
  • 安全模型可借鉴:即使是对外公开、且已经刻意降低敏感度的密钥集合,Expo 团队依然采用"权威存储于云 Secret Manager、本地按需解密、gitignore 防提交、模板占位符注入"的多层纵深防御设计,并明确禁止 keystore 等高危凭据入库——这套"密钥不进仓库、构建期注入、可回退可撤销"的模式,同样值得其他开源项目参考。

如果想进一步验证密钥消费链路,可以依次查看 template-files/keys.json(占位值)、tools/src/dynamic-macros/generateDynamicMacros.ts(Android 读取逻辑)、apps/expo-go/ios/Build-Phases/generate-dynamic-macros.sh(iOS 读取与替换逻辑),以及 template-files/ios/GoogleService-Info.plist 与 template-files/android/google-services.json(占位符模板实例)。

【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 17:13:18

MATLAB均匀量化仿真:精准建模ADC量化误差与SNR验证

简介:本资源是一套面向信号处理初学者与通信工程学生的MATLAB均匀量化仿真实验材料,聚焦量化误差成因、建模与性能评估等核心问题。资源包含3个关键文件:1个MATLAB主脚本(Untitled2.m)实现正弦/语音信号的采样、8级均匀…

作者头像 李华
网站建设 2026/9/10 17:11:09

TVBoxOSC文档查看器:3步把电视盒子变成大屏文档阅读台

TVBoxOSC文档查看器:3步把电视盒子变成大屏文档阅读台 【免费下载链接】TVBoxOSC TVBoxOSC - 一个基于第三方项目的代码库,用于电视盒子的控制和管理。 项目地址: https://gitcode.com/GitHub_Trending/tv/TVBoxOSC TVBoxOSC是一个面向电视盒子的…

作者头像 李华
网站建设 2026/9/10 17:09:16

JAVA毕设选题推荐:基于 Web 的实验室耗材全生命周期管理平台的设计与实现 基于 Web 技术的实验室耗材管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华
网站建设 2026/9/10 17:09:10

Kanass任务管理工具:敏捷开发的高效实践指南

1. Kanass任务管理工具概述Kanass是一款面向技术团队的任务管理工具,特别适合敏捷开发场景。它通过看板(Kanban)和任务列表(Task List)的有机结合,帮助开发团队实现任务可视化、流程标准化和协作高效化。我…

作者头像 李华
网站建设 2026/9/10 17:08:33

OpenSSL 中的 SLH-DSA(SLH-DSA)状态哈希签名实现设计详解

OpenSSL 中的 SLH-DSA(SLH-DSA)状态哈希签名实现设计详解 【免费下载链接】openssl General purpose TLS and crypto library 项目地址: https://gitcode.com/GitHub_Trending/ope/openssl 导读 SLH-DSA(Stateless Hash-Based Digita…

作者头像 李华