跳到主要内容

安全性

构建应用程序时,安全性常常被忽视。 诚然,构建完全坚不可摧的软件是不可能的——我们还没有发明出完全坚不可摧的锁(毕竟,银行金库仍然会被攻破)。 然而,成为恶意攻击的受害者或暴露安全漏洞的可能性与您愿意投入保护您的应用程序免受任何此类意外事件影响的努力成反比。 虽然普通的挂锁可以被撬开,但它仍然比橱柜挂钩更难破解!

在本指南中,您将了解存储敏感信息、身份验证、网络安全以及帮助您保护应用程序安全的工具的最佳实践。 这不是一个飞行前检查清单——而是一个选项目录,其中的每个选项都将有助于进一步保护您的应用程序和用户。

存储敏感信息

永远不要在您的应用程序代码中存储敏感的 API 密钥。 任何包含在您的代码中的内容都可能被任何检查应用程序包的人以纯文本形式访问。 诸如 react-native-dotenvreact-native-config 之类的工具非常适合添加特定于环境的变量(如 API 端点),但不应将它们与服务器端环境变量混淆,后者通常可能包含密钥和 API 密钥。

如果您必须拥有 API 密钥或密钥才能从您的应用程序访问某些资源,则最安全的方法是在您的应用程序和资源之间构建一个编排层。 这可以是一个无服务器函数(例如,使用 AWS Lambda 或 Google Cloud Functions),它可以转发带有必需 API 密钥或密钥的请求。 API 使用者无法像访问应用程序代码中的密钥那样访问服务器端代码中的密钥。

对于持久化用户数据,请根据其敏感性选择正确的存储类型。 当您的应用程序被使用时,您通常会发现需要在设备上保存数据,无论是为了支持您的应用程序离线使用、减少网络请求还是在会话之间保存用户的访问令牌,这样他们就不必在每次使用应用程序时重新进行身份验证。

持久化与非持久化 — 持久化数据被写入设备的磁盘,这使得您的应用程序可以在应用程序启动后读取数据,而无需执行另一个网络请求来获取它或要求用户重新输入它。 但这也可能使该数据更容易受到攻击者的访问。 非持久化数据永远不会写入磁盘——因此没有数据可以访问!

Async Storage

Async Storage 是一个由社区维护的 React Native 模块,它提供异步、未加密的键值存储。 Async Storage 不在应用程序之间共享:每个应用程序都有自己的沙箱环境,并且无法访问来自其他应用程序的数据。

在以下情况下使用 async storage...不要在以下情况下使用 async storage...
在应用程序运行期间持久化非敏感数据令牌存储
持久化 Redux 状态密钥
持久化 GraphQL 状态
存储全局应用程序范围的变量

开发者笔记

Async Storage 是 Web 中 Local Storage 的 React Native 等效项

Secure Storage

React Native 没有捆绑任何存储敏感数据的方法。 但是,Android 和 iOS 平台已经存在解决方案。

iOS - Keychain Services

Keychain Services 允许您为用户安全地存储小块敏感信息。 这是存储证书、令牌、密码和任何其他不属于 Async Storage 的敏感信息的理想场所。

Android - Secure Shared Preferences

Shared Preferences 是 Android 中持久键值数据存储的等效项。 Shared Preferences 中的数据默认情况下未加密,但 Encrypted Shared Preferences 包装了 Android 的 Shared Preferences 类,并自动加密键和值。

Android - Keystore

Android Keystore 系统允许您将加密密钥存储在容器中,使其更难从设备中提取。

为了使用 iOS Keychain 服务或 Android Secure Shared Preferences,您可以自己编写桥接器,也可以使用为您包装它们的库并以您自己的风险提供统一的 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 将向用户显示一个消除歧义对话框,并要求他们选择要使用哪个应用程序来打开链接。 但是,在 iOS 上,操作系统将为您做出选择,因此用户将毫不知情。 Apple 已采取措施在较新的 iOS 版本(iOS 11)中解决此问题,他们在其中实行了先到先得的原则,尽管这种漏洞仍然可以通过不同的方式利用,您可以在此处阅读更多相关信息。 使用通用链接将允许在 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_verifiercode_challenge。 在 authorize 请求正确返回后,客户端还会发送用于生成 code_challengecode_verifier。 然后,IDP 将计算 code_challenge,查看它是否与在第一个 /authorize 请求中设置的内容匹配,并且仅在值匹配时发送访问令牌。

这保证只有触发初始授权流程的应用程序才能成功地将验证码交换为 JWT。 因此,即使恶意应用程序获得了对验证码的访问权限,它本身也是无用的。 要查看实际效果,请查看此示例

用于原生 OAuth 的一个值得考虑的库是 react-native-app-auth。 React-native-app-auth 是一个用于与 OAuth2 提供程序通信的 SDK。 它包装了原生 AppAuth-iOSAppAuth-Android 库,并且可以支持 PKCE。

只有当您的身份提供商支持 PKCE 时,React-native-app-auth 才能支持 PKCE。

OAuth2 with PKCE

网络安全

您的 API 应始终使用 SSL 加密。 SSL 加密可防止请求的数据在离开服务器和到达客户端之间以纯文本形式被读取。 您会知道端点是安全的,因为它以 https:// 而不是 http:// 开头。

SSL Pinning

使用 https 端点仍然可能使您的数据容易受到拦截。 使用 https,客户端只有在服务器可以提供由预安装在客户端上的受信任证书颁发机构签名的有效证书时才会信任该服务器。 攻击者可以通过在用户的设备上安装恶意根 CA 证书来利用这一点,因此客户端将信任所有由攻击者签名的证书。 因此,仅依赖证书仍然可能使您容易受到中间人攻击

SSL pinning 是一种可以在客户端使用的技术,以避免这种攻击。 它的工作原理是在开发期间将受信任证书的列表嵌入(或固定)到客户端,以便只接受使用受信任证书之一签名的请求,并且任何自签名证书都不会被接受。

使用 SSL pinning 时,您应该注意证书到期。 证书每 1-2 年过期一次,当证书过期时,需要在应用程序和服务器上都进行更新。 一旦服务器上的证书已更新,任何嵌入旧证书的应用程序都将停止工作。

总结

没有万无一失的方法来处理安全性,但通过有意识的努力和勤奋,可以显着降低应用程序中发生安全漏洞的可能性。 安全方面的投入应与应用程序中存储的数据的敏感性、用户数量以及黑客访问其帐户时可能造成的损害成正比。 请记住:访问从未被请求的信息要困难得多。