在应用开发中,安全性往往是被忽视的一环。虽然世界上不存在绝对"攻不破"的软件(毕竟连银行金库都会被盗),但被攻击的概率与你愿意投入的防护精力成反比。虽然一把普通的挂锁可以被撬开,但它依然比一个简单的柜门挂钩难对付得多!
本文将为你梳理 React Native 开发中的安全最佳实践,涵盖敏感信息存储 、身份验证 、网络安全等核心领域。
一、 敏感信息的存储 (Storing Sensitive Info)
1. 永远不要在代码中硬编码密钥
原则: 永远不要将敏感的 API 密钥(API Secrets)直接写在代码里。
任何包含在 App 代码包(Bundle)中的内容,都可以被他人反编译并以明文形式读取。虽然像 react-native-dotenv 和 react-native-config 这样的工具很适合管理 API 端点等环境配置,但切勿混淆,它们不适合存储真正的服务器端机密信息。
最佳方案:中间层编排
如果你必须使用密钥来访问某些资源,最安全的方法是构建一个编排层 (Orchestration Layer)。
- 使用无服务器函数(如 AWS Lambda 或 Google Cloud Functions)。
- 让 App 请求这个中间层,由中间层附带密钥转发请求给目标服务。
- 这样,密钥只存在于服务器端,客户端无法触及。
2. 选择正确的存储方式:持久化数据
根据数据的敏感程度选择存储方案。
❌ Async Storage (异步存储)
Async Storage 是 React Native 社区维护的一个模块,提供异步的、未加密的键值对存储。
- 特性: 每个 App 拥有独立的沙箱,不能跨 App 共享。
- 本质: 它相当于 Web 开发中的
localStorage。
| ✅ 适合存放 (DO) | ❌ 禁止存放 (DON'T) |
|---|---|
| 跨启动的非敏感数据 | 用户 Token (Access Tokens) |
| Redux / GraphQL 状态持久化 | 密码、密钥 (Secrets) |
| 全局 App 变量 | 个人隐私信息 |
✅ Secure Storage (安全存储)
React Native 并没有内置敏感数据存储功能。你需要依赖原生平台的解决方案:
-
iOS - Keychain Services (钥匙串服务):
iOS 系统提供的安全容器,适合存储证书、Token、密码等小块敏感数据。
-
Android - Secure Shared Preferences / Keystore:
Shared Preferences本身是不加密的。- 使用
Encrypted Shared Preferences可以自动加密键值。 Android Keystore系统允许你将加密密钥存储在容器中,使其难以被从设备中提取。
推荐库:
为了统一调用 iOS 和 Android 的安全存储,建议使用社区封装好的库:
expo-secure-storereact-native-keychain
⚠️ 警告 (Caution):
小心"无意中"泄露信息。例如:你可能将包含用户 Token 或敏感表单数据的整个 Redux 状态树(State Tree)持久化到了 Async Storage 中;或者将用户隐私数据发送到了 Sentry 或 Crashlytics 等监控平台。
二、 身份验证与深度链接 (Authentication and Deep Linking)
移动应用有一个 Web 端不存在的独特漏洞:深度链接 (Deep Linking) 。
1. 深度链接的风险
深度链接(如 app://products/1)允许外部来源直接唤起你的 App 并传递数据。
- 安全隐患: 深度链接是不安全的,永远不要通过它传递敏感信息。
- 原因: 操作系统没有中心化的机制来注册 URL Scheme。任何恶意 App 都可以注册和你一样的
app://协议。 - 后果: 如果你通过链接发送 Token,恶意 App 可以通过"劫持"这个链接来获取你的 Token。
注: iOS 11 之后引入了"先到先得"原则,Apple 也推出了 Universal Links (通用链接) 来解决此问题,它比自定义 Scheme 更安全。
2. OAuth2 与重定向攻击
OAuth2 是目前最流行的认证协议。在 Web 上,认证后的回调重定向是安全的(因为 URL 唯一)。但在 App 中,由于上述的深度链接劫持风险,传统的重定向不再安全。
3. 解决方案:PKCE (密钥代码交换证明)
为了解决这个问题,OAuth2 扩展了 PKCE (Proof of Key Code Exchange,读作 "Pixy") 机制。它通过验证"发起请求的客户端"和"交换 Token 的客户端"是否是同一个,来防止劫持。
PKCE 工作原理:
- 生成: 客户端生成一个随机字符串
code_verifier,并对其进行 SHA-256 哈希得到code_challenge。 - 请求: 客户端发起
/authorize请求时,带上code_challenge。 - 验证: 用户认证成功后,客户端用拿到的 Code 换取 Token 时,必须带上原始的
code_verifier。 - 防守: 服务器验证
code_verifier的哈希值是否与第一步收到的code_challenge匹配。如果不匹配(说明有人中途截获了 Code),则拒绝颁发 Token。
推荐库:
使用 react-native-app-auth,它封装了原生的 AppAuth 库并支持 PKCE。
三、 网络安全 (Network Security)
1. SSL 加密 (HTTPS)
所有的 API 请求都必须使用 HTTPS。这能防止数据在传输过程中被明文读取。
2. SSL Pinning (证书锁定)
即使使用了 HTTPS,数据仍可能被中间人攻击 (Man-in-the-Middle Attack) 拦截。
- 场景: 攻击者诱导用户在设备上安装一个恶意的根证书(Root CA)。由于系统信任这个根证书,攻击者就可以伪造你的服务器证书,解密 HTTPS 流量。
- 防御: 使用 SSL Pinning。
- 原理: 在 App 代码中内置(锁定)你服务器的证书指纹。App 发起请求时,不仅检查证书是否有效,还会检查它是否就是你内置的那个。如果是攻击者的证书,直接拒绝连接。
⚠️ 风险提示:
证书是有过期时间的(通常 1-2 年)。一旦服务器更新了证书,而用户手里的 App 还是旧版本(内置旧证书),App 将无法联网。因此使用 SSL Pinning 需要非常完善的客户端更新机制和证书轮换策略。
总结
安全没有"银弹"。你无法做到 100% 的安全,但可以通过层层防御来提高攻击成本:
- 存储: 敏感数据进 Keychain/Keystore,普通数据进 Async Storage。
- 密钥: 别把 API Secrets 写在代码里,用后端代理。
- 认证: 使用 OAuth2 + PKCE 防止 Token 被劫持。
- 传输: 全程 HTTPS,对高安全需求场景使用 SSL Pinning。
记住:不去请求不需要的数据,是保护数据的最好方式。