安全
在构建应用程序时,安全性常常被忽视。确实,构建一个完全无懈可击的软件是不可能的——我们还没有发明出一种完全无法攻破的锁(毕竟,银行金库仍然会被盗)。然而,遭受恶意攻击或暴露安全漏洞的概率与您愿意投入保护应用程序以防范此类事件的努力成反比。尽管普通的挂锁可以被撬开,但它仍然比橱柜挂钩更难逾越!
在本指南中,您将学习存储敏感信息、身份验证、网络安全以及帮助您保护应用程序安全的工具的最佳实践。这并不是一份飞行前检查清单——它是一个选项目录,其中每个选项都将有助于进一步保护您的应用程序和用户。
存储敏感信息
切勿在您的应用代码中存储敏感的 API 密钥。任何包含在您的代码中的内容都可能被任何检查应用包的人以纯文本形式访问。像 react-native-dotenv 和 react-native-config 这样的工具非常适合添加特定于环境的变量,如 API 端点,但它们不应与服务器端环境变量混淆,后者通常包含秘密和 API 密钥。
如果您必须拥有 API 密钥或秘密才能从应用访问某些资源,最安全的方法是在应用和资源之间构建一个编排层。这可以是一个无服务器函数(例如使用 AWS Lambda 或 Google Cloud Functions),它可以转发带有所需 API 密钥或秘密的请求。服务器端代码中的秘密无法像应用代码中的秘密那样被 API 使用者访问。
对于持久化用户数据,请根据其敏感性选择正确的存储类型。 随着您的应用使用,您经常会发现需要在设备上保存数据,无论是为了支持应用离线使用、减少网络请求,还是为了在会话之间保存用户的访问令牌,这样他们每次使用应用时就不必重新进行身份验证。
持久化与非持久化 — 持久化数据写入设备的磁盘,这使得您的应用可以在多次应用启动之间读取数据,而无需再次进行网络请求来获取或要求用户重新输入。但这同时也可能使数据更容易受到攻击者的访问。非持久化数据从不写入磁盘——因此没有数据可供访问!
异步存储 (Async Storage)
异步存储 (Async Storage) 是一个由社区维护的 React Native 模块,提供一个异步、未加密的键值存储。异步存储不在应用之间共享:每个应用都有自己的沙盒环境,无法访问其他应用的数据。
应在以下情况使用异步存储... | 不应将异步存储用于... |
---|---|
在应用运行之间持久化非敏感数据 | 令牌存储 |
持久化 Redux 状态 | 秘密信息 |
持久化 GraphQL 状态 | |
存储全局应用变量 |
开发者须知
- Web
异步存储 (Async Storage) 是 React Native 中等同于 Web 端本地存储 (Local Storage) 的功能
安全存储
React Native 没有捆绑任何存储敏感数据的方式。然而,Android 和 iOS 平台都有现成的解决方案。
iOS - 钥匙串服务 (Keychain Services)
钥匙串服务 (Keychain Services) 允许您安全地存储用户的少量敏感信息。这是存储证书、令牌、密码以及任何不属于异步存储 (Async Storage) 的其他敏感信息的理想场所。
Android - 安全共享偏好设置 (Secure Shared Preferences)
共享偏好设置 (Shared Preferences) 是 Android 中持久化键值数据存储的等效功能。共享偏好设置中的数据默认不加密,但 加密共享偏好设置 (Encrypted Shared Preferences) 封装了 Android 的 Shared Preferences 类,并自动加密键和值。
Android - 密钥库 (Keystore)
Android 密钥库 (Keystore) 系统允许您将加密密钥存储在一个容器中,使其更难从设备中提取。
为了使用 iOS 钥匙串服务或 Android 安全共享偏好设置,您可以自行编写一个桥接器,或者使用一个为您封装它们并提供统一 API 的库,但这需要您自担风险。值得考虑的一些库:
注意避免无意中存储或暴露敏感信息。 这可能意外发生,例如将敏感的表单数据保存在 Redux 状态中,并将整个状态树持久化到异步存储 (Async Storage) 中。或者将用户令牌和个人信息发送到应用程序监控服务,如 Sentry 或 Crashlytics。
身份验证和深度链接
移动应用程序存在一个 Web 端不存在的独特漏洞:深度链接。深度链接是一种将数据从外部源直接发送到原生应用程序的方式。深度链接类似于 app://
,其中 app
是您的应用方案,// 后面的任何内容都可以在内部用于处理请求。
例如,如果您正在构建一个电子商务应用,您可以使用 app://products/1
来深度链接到您的应用,并打开 ID 为 1 的产品的详细信息页面。您可以将其类比为 Web 上的 URL,但有一个关键区别:
深度链接不安全,您绝不应在其中发送任何敏感信息。
深度链接不安全的原因在于没有集中的方法来注册 URL 方案。作为应用程序开发人员,您可以通过 在 iOS 上于 Xcode 中配置 或 在 Android 上添加意图 (intent) 来使用您选择的几乎任何 URL 方案。
没有什么能阻止恶意应用程序通过注册相同的方案来劫持您的深度链接,然后获取您的链接中包含的数据。发送诸如 app://products/1
之类的信息并无害处,但发送令牌则是一个安全问题。
当操作系统在打开链接时有两个或更多应用程序可供选择时,Android 会向用户显示一个 选择对话框 (Disambiguation dialog),并要求他们选择使用哪个应用程序打开链接。然而,在 iOS 上,操作系统会为您做出选择,因此用户会对此一无所知。苹果已在较新的 iOS 版本(iOS 11)中采取措施解决此问题,他们引入了“先到先得”的原则,尽管此漏洞仍可能以不同方式被利用,您可以在 此处 阅读更多信息。使用 通用链接 (universal links) 将允许在 iOS 中安全地链接到您应用内的内容。
OAuth2 和重定向
OAuth2 身份验证协议如今非常流行,被誉为最完整和安全的协议。OpenID Connect 协议也基于此。在 OAuth2 中,用户被要求通过第三方进行身份验证。成功完成后,该第三方会将验证码重定向回请求应用程序,该验证码可以换取一个 JWT——即 JSON Web Token。JWT 是一个用于在 Web 上安全传输信息的开放标准。
在 Web 上,此重定向步骤是安全的,因为 Web 上的 URL 保证是唯一的。对于应用程序来说则不然,因为如前所述,没有集中的方法来注册 URL 方案!为了解决此安全问题,必须以 PKCE 的形式添加额外的检查。
PKCE,发音为“Pixy”,是“密钥交换证明 (Proof of Key Code Exchange)”的缩写,是 OAuth 2 规范的扩展。这涉及添加额外的安全层,以验证身份验证和令牌交换请求来自同一客户端。PKCE 使用 SHA 256 加密哈希算法。SHA 256 为任何大小的文本或文件创建唯一的“签名”,但它:
- 无论输入文件大小如何,长度始终相同
- 保证对相同的输入始终产生相同的结果
- 单向性(即,您无法对其进行逆向工程以揭示原始输入)
现在您有两个值:
- code_verifier - 客户端生成的一个大的随机字符串
- code_challenge - code_verifier 的 SHA 256 值
在最初的 /authorize
请求期间,客户端还会发送其内存中保存的 code_verifier
的 code_challenge
。在授权请求正确返回后,客户端还会发送用于生成 code_challenge
的 code_verifier
。然后,身份提供者 (IDP) 将计算 code_challenge
,查看它是否与最初的 /authorize
请求中设置的值匹配,并且仅当值匹配时才发送访问令牌。
这保证只有触发初始授权流程的应用程序才能成功将验证码换取 JWT。因此,即使恶意应用程序获得验证码,它本身也无用。要查看实际操作,请查看 此示例。
针对原生 OAuth,可以考虑使用库 react-native-app-auth。React-native-app-auth 是一个用于与 OAuth2 提供商通信的 SDK。它封装了原生的 AppAuth-iOS 和 AppAuth-Android 库,并支持 PKCE。
只有当您的身份提供者支持 PKCE 时,React-native-app-auth 才能支持 PKCE。
网络安全
您的 API 应该始终使用 SSL 加密。SSL 加密可以防止在请求数据离开服务器和到达客户端之间以纯文本形式被读取。您会知道端点是安全的,因为它以 https://
而非 http://
开头。
SSL 证书绑定 (SSL Pinning)
使用 https 端点仍然可能使您的数据容易受到拦截。对于 https,客户端只有在服务器能提供由预装在客户端上的受信任证书颁发机构签名的有效证书时,才会信任该服务器。攻击者可以利用这一点,通过在用户的设备上安装恶意根 CA 证书,使客户端信任所有由攻击者签名的证书。因此,仅依靠证书仍然可能使您容易受到 中间人攻击。
SSL 证书绑定 (SSL pinning) 是一种可以在客户端用于避免此攻击的技术。它的工作原理是在开发过程中将一组受信任的证书嵌入(或绑定)到客户端,这样只有使用受信任证书之一签名的请求才会被接受,而任何自签名证书则不会被接受。
使用 SSL 证书绑定时,您应该注意证书的有效期。证书每 1-2 年过期一次,一旦过期,就需要同时在应用和服务器上进行更新。一旦服务器上的证书更新,任何嵌入旧证书的应用将停止工作。
总结
处理安全问题没有万无一失的方法,但通过有意识的努力和勤奋,可以显著降低应用程序发生安全漏洞的可能性。投资于安全性应与应用程序中存储的数据的敏感性、用户数量以及黑客在获得其帐户访问权限后可能造成的损害成正比。请记住:从一开始就未请求的信息,其访问难度会显著增加。