安全
在构建应用程序时,安全问题常常被忽视。诚然,构建完全无法攻破的软件是不可能的——我们还没有发明出完全无法攻破的锁(毕竟银行金库仍然会被盗)。但是,成为恶意攻击受害者或暴露安全漏洞的可能性与您愿意为防止此类事件而付出的努力成反比。虽然普通的挂锁是可以被撬开的,但它仍然比柜子上的挂钩更难打开!
在本指南中,您将学习有关存储敏感信息、身份验证、网络安全以及帮助您保护应用程序的工具的最佳实践。这不是一份飞行前检查清单——它是一个选项目录,每个选项都有助于进一步保护您的应用程序和用户。
存储敏感信息
切勿在应用程序代码中存储敏感的 API 密钥。代码中包含的任何内容都可能被检查应用程序包的任何人以明文方式访问。像 react-native-dotenv 和 react-native-config 这样的工具非常适合添加特定于环境的变量,例如 API 端点,但不要将其与服务器端环境变量混淆,服务器端环境变量通常包含机密和 API 密钥。
如果您必须拥有一个 API 密钥或一个机密才能从您的应用程序访问某些资源,那么处理此问题的最安全方法是在您的应用程序和资源之间构建一个编排层。这可能是一个无服务器函数(例如,使用 AWS Lambda 或 Google Cloud Functions),它可以转发带有所需 API 密钥或机密的请求。服务器端代码中的机密无法像应用程序代码中的机密那样被 API 消费者访问。
**对于持久化用户数据,请根据其敏感性选择合适的存储类型。** 在使用您的应用程序时,您经常会发现需要在设备上保存数据,无论是为了支持您的应用程序离线使用,减少网络请求,还是在会话之间保存用户的访问令牌,以便他们无需每次使用应用程序时都重新进行身份验证。
**持久化与非持久化**——持久化数据写入设备的磁盘,这允许应用程序在应用程序启动时读取数据,而无需执行另一个网络请求来获取它或要求用户重新输入它。但这也会使该数据更容易受到攻击者的访问。非持久化数据永远不会写入磁盘——因此没有数据可以访问!
Async Storage
Async Storage 是一个由社区维护的 React Native 模块,它提供了一个异步的、未加密的键值存储。Async Storage 不会在应用程序之间共享:每个应用程序都有自己的沙箱环境,并且无法访问其他应用程序的数据。
**请**在以下情况下使用异步存储... | **请勿**在以下情况下使用异步存储... |
---|---|
在应用程序运行期间持久化非敏感数据 | 令牌存储 |
持久化 Redux 状态 | 机密 |
持久化 GraphQL 状态 | |
存储全局应用程序范围的变量 |
开发者笔记
- Web
Async Storage 等同于 Web 中的 Local Storage
安全存储
React Native 本身不包含任何存储敏感数据的方法。但是,Android 和 iOS 平台已经存在一些解决方案。
iOS - Keychain Services
Keychain Services 允许您安全地存储少量敏感信息供用户使用。这是存储证书、令牌、密码以及任何其他不属于 Async Storage 的敏感信息的理想位置。
Android - 安全共享首选项
Shared Preferences 是 Android 中持久化键值数据存储的等效项。**Shared Preferences 中的数据默认情况下未加密**,但 加密的 Shared Preferences 包装了 Android 的 Shared Preferences 类,并自动加密键和值。
Android - Keystore
Android Keystore 系统允许您将加密密钥存储在容器中,以使其更难以从设备中提取。
为了使用 iOS Keychain Services 或 Android 安全共享首选项,您可以自己编写桥接代码,也可以使用包装它们的库并提供统一的 API(需自行承担风险)。一些值得考虑的库
**注意不要无意中存储或暴露敏感信息。** 这可能意外发生,例如,在 redux 状态中保存敏感表单数据,并在 Async Storage 中持久化整个状态树。或者将用户令牌和个人信息发送到应用程序监控服务,例如 Sentry 或 Crashlytics。
身份验证和深度链接
移动应用程序存在 Web 中不存在的独特漏洞:**深度链接**。深度链接是从外部来源直接向原生应用程序发送数据的一种方式。深度链接看起来像 app://
,其中 app
是您的应用程序方案,//
后面的任何内容都可以在内部用于处理请求。
例如,如果您正在构建一个电子商务应用程序,您可以使用 app://products/1
深度链接到您的应用程序并打开产品 ID 为 1 的产品详情页面。您可以将其视为 Web 上的 URL,但有一个关键的区别
深度链接不安全,您不应在其中发送任何敏感信息。
深度链接不安全的原因是,没有集中注册 URL 方案的方法。作为应用程序开发人员,您可以通过 在 Xcode 中进行配置(适用于 iOS)或 在 Android 上添加意图 来使用您选择的几乎任何 URL 方案。
没有什么可以阻止恶意应用程序通过注册到相同的方案来劫持您的深度链接,然后获取您的链接包含的数据。发送类似 app://products/1
的内容无害,但发送令牌则存在安全问题。
当操作系统在打开链接时有两个或多个应用程序可供选择时,Android 会向用户显示一个 歧义对话框 并要求他们选择哪个应用程序来打开链接。但在 iOS 上,操作系统会为您做出选择,因此用户会浑然不觉。Apple 在更高版本的 iOS(iOS 11)中采取措施解决了这个问题,他们实施了先到先得的原则,尽管此漏洞仍可以通过其他方式被利用,您可以 在此处 阅读更多相关信息。使用 通用链接 将允许在 iOS 中安全地链接到应用程序中的内容。
OAuth2 和重定向
OAuth2 身份验证协议如今非常流行,被认为是最完整和最安全的协议。OpenID Connect 协议也基于此。在 OAuth2 中,用户会被要求通过第三方进行身份验证。成功完成后,此第三方会将验证代码重定向回请求应用程序,该代码可以交换为 JWT——JSON Web 令牌。JWT 是一种在 Web 上安全传输各方之间信息的开放标准。
在 Web 上,此重定向步骤是安全的,因为 Web 上的 URL 保证是唯一的。但这对于应用程序来说并不适用,因为如前所述,没有集中注册 URL 方案的方法!为了解决此安全问题,必须以 PKCE 的形式添加额外的检查。
PKCE(发音为“Pixy”)代表密钥代码交换证明,它是 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 才能支持它。
网络安全
您的 API 应始终使用SSL 加密。SSL 加密可以防止请求数据在离开服务器到到达客户端之前以明文形式读取。您会知道端点是安全的,因为它以 https://
而不是 http://
开头。
SSL 固定
使用 https 端点仍然可能使您的数据容易受到拦截。使用 https 时,客户端只有在服务器能够提供由客户端预安装的可信证书颁发机构签名的有效证书时才会信任该服务器。攻击者可以通过将恶意的根 CA 证书安装到用户的设备上,从而利用这一点,因此客户端将信任由攻击者签名的所有证书。因此,仅依赖证书仍然可能使您容易受到中间人攻击。
SSL 固定是一种可以在客户端上使用的技术,以避免这种攻击。它的工作原理是在开发过程中将受信任证书列表嵌入(或固定)到客户端,以便仅接受使用其中一个受信任证书签名的请求,并且任何自签名证书都不会被接受。
在使用 SSL 固定时,您应该注意证书到期。证书每 1-2 年到期一次,到期后需要在应用程序和服务器上更新。一旦服务器上的证书更新,任何嵌入旧证书的应用程序都将停止工作。
总结
没有万无一失的安全处理方法,但通过有意识的努力和勤勉,可以显著降低应用程序发生安全漏洞的可能性。根据应用程序中存储的数据的敏感性、用户数量以及黑客访问其帐户时可能造成的损害,投入与之相称的安全措施。记住:访问从未请求的信息要困难得多。