Skip to main content

安全

构建应用时,安全性常常被忽视。确实,构建完全无法穿透的软件是不可能的 - 我们还没有发明出完全无法穿透的锁(毕竟,银行金库仍然会被闯入)。然而,成为恶意攻击受害者或暴露安全漏洞的可能性与你愿意为保护应用免受任何此类意外情况而付出的努力成反比。普通的挂锁虽然可以撬开,但还是比柜子钩子难撬多了!

¥Security is often overlooked when building apps. It is true that it is impossible to build software that is completely impenetrable—we’ve yet to invent a completely impenetrable lock (bank vaults do, after all, still get broken into). However, the probability of falling victim to a malicious attack or being exposed for a security vulnerability is inversely proportional to the effort you’re willing to put in to protecting your application against any such eventuality. Although an ordinary padlock is pickable, it is still much harder to get past than a cabinet hook!

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

¥In this guide, you will learn about best practices for storing sensitive information, authentication, network security, and tools that will help you secure your app. This is not a preflight checklist—it is a catalogue of options, each of which will help further protect your app and users.

存储敏感信息

¥Storing Sensitive Info

切勿在应用代码中存储敏感的 API 密钥。任何检查应用包的人都可以以纯文本形式访问代码中包含的任何内容。像 react-native-dotenvreact-native-config 这样的工具非常适合添加特定于环境的变量(例如 API 端点),但它们不应该与服务器端环境变量混淆,服务器端环境变量通常包含密钥和 API 密钥。

¥Never store sensitive API keys in your app code. Anything included in your code could be accessed in plain text by anyone inspecting the app bundle. Tools like react-native-dotenv and react-native-config are great for adding environment-specific variables like API endpoints, but they should not be confused with server-side environment variables, which can often contain secrets and API keys.

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

¥If you must have an API key or a secret to access some resource from your app, the most secure way to handle this would be to build an orchestration layer between your app and the resource. This could be a serverless function (e.g. using AWS Lambda or Google Cloud Functions) which can forward the request with the required API key or secret. Secrets in server side code cannot be accessed by the API consumers the same way secrets in your app code can.

对于持久的用户数据,请根据其敏感性选择正确的存储类型。在使用你的应用时,你经常会发现需要在设备上保存数据,无论是支持你的应用离线使用、减少网络请求还是在会话之间保存用户的访问令牌,以便他们不必重新启动 - 每次使用该应用时进行身份验证。

¥For persisted user data, choose the right type of storage based on its sensitivity. As your app is used, you’ll often find the need to save data on the device, whether to support your app being used offline, cut down on network requests or save your user’s access token between sessions so they wouldn’t have to re-authenticate each time they use the app.

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

¥Persisted vs unpersisted — persisted data is written to the device’s disk, which lets the data be read by your app across application launches without having to do another network request to fetch it or asking the user to re-enter it. But this also can make that data more vulnerable to being accessed by attackers. Unpersisted data is never written to disk—so there's no data to access!

异步存储

¥Async Storage

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

¥Async Storage is a community-maintained module for React Native that provides an asynchronous, unencrypted, key-value store. Async Storage is not shared between apps: every app has its own sandbox environment and has no access to data from other apps.

当...时请使用异步存储不要将异步存储用于...
在应用运行期间保留非敏感数据令牌存储
持久化 Redux 状态秘密
持久化 GraphQL 状态
存储全局应用范围的变量

开发者注意事项

¥Developer Notes

异步存储是 React Native 相当于网络上的本地存储

¥Async Storage is the React Native equivalent of Local Storage from the web

安全存储

¥Secure Storage

React Native 不与任何存储敏感数据的方式打包在一起。然而,Android 和 iOS 平台已有解决方案。

¥React Native does not come bundled with any way of storing sensitive data. However, there are pre-existing solutions for Android and iOS platforms.

iOS - Keychain 服务

¥iOS - Keychain Services

Keychain 服务 允许你安全地为用户存储小块敏感信息。这是存储证书、令牌、密码和任何其他不属于异步存储的敏感信息的理想位置。

¥Keychain Services allows you to securely store small chunks of sensitive info for the user. This is an ideal place to store certificates, tokens, passwords, and any other sensitive information that doesn’t belong in Async Storage.

安卓 - 保护共享首选项

¥Android - Secure Shared Preferences

共享偏好设置 是 Android 中等效的持久键值数据存储。默认情况下,共享首选项中的数据不加密,但 加密的共享首选项 封装了 Android 的共享首选项类,并自动加密键和值。

¥Shared Preferences is the Android equivalent for a persistent key-value data store. Data in Shared Preferences is not encrypted by default, but Encrypted Shared Preferences wraps the Shared Preferences class for Android, and automatically encrypts keys and values.

安卓 - 密钥库

¥Android - Keystore

安卓密钥库 系统允许你将加密密钥存储在容器中,从而更难以从设备中提取密钥。

¥The Android Keystore system lets you store cryptographic keys in a container to make it more difficult to extract from the device.

为了使用 iOS 密钥链服务或 Android 安全共享首选项,你可以自己编写一个桥,也可以使用一个为你封装它们并提供统一 API 的库,风险由你自行承担。一些需要考虑的库:

¥In order to use iOS Keychain services or Android Secure Shared Preferences, you can either write a bridge yourself or use a library which wraps them for you and provides a unified API at your own risk. Some libraries to consider:

请注意无意中存储或泄露敏感信息。这可能会意外发生,例如将敏感表单数据保存在 redux 状态中并将整个状态树保留在异步存储中。或者将用户令牌和个人信息发送到应用监控服务,例如 Sentry 或 Crashlytics。

¥Be mindful of unintentionally storing or exposing sensitive info. This could happen accidentally, for example saving sensitive form data in redux state and persisting the whole state tree in Async Storage. Or sending user tokens and personal info to an application monitoring service such as Sentry or Crashlytics.

身份验证和深度链接

¥Authentication and Deep Linking

移动应用具有网络中不存在的独特漏洞:深度链接。深度链接是一种从外部源直接向原生应用发送数据的方法。深层链接看起来像 app://,其中 app 是你的应用方案, // 后面的任何内容都可以在内部使用来处理请求。

¥Mobile apps have a unique vulnerability that is non-existent in the web: deep linking. Deep linking is a way of sending data directly to a native application from an outside source. A deep link looks like app:// where app is your app scheme and anything following the // could be used internally to handle the request.

例如,如果你正在构建电子商务应用,则可以使用 app://products/1 深层链接到你的应用并打开 id 1 的产品的产品详细信息页面。你可以将这些类似于网络上的 URL,但有一个关键区别:

¥For example, if you were building an ecommerce app, you could use app://products/1 to deep link to your app and open the product detail page for a product with id 1. You can think of these kind of like URLs on the web, but with one crucial distinction:

深层链接并不安全,你不应该在其中发送任何敏感信息。

¥Deep links are not secure and you should never send any sensitive information in them.

深层链接不安全的原因是没有集中的方法来注册 URL 方案。作为应用开发者,你几乎可以使用 在 Xcode 中配置它 for iOS 或 在 Android 上添加意图 选择的任何 url 方案。

¥The reason deep links are not secure is because there is no centralized method of registering URL schemes. As an application developer, you can use almost any url scheme you choose by configuring it in Xcode for iOS or adding an intent on Android.

没有什么可以阻止恶意应用通过注册相同的方案来劫持你的深层链接,然后获取对你的链接包含的数据的访问权限。发送像 app://products/1 这样的东西并没有什么害处,但是发送令牌是一个安全问题。

¥There is nothing stopping a malicious application from hijacking your deep link by also registering to the same scheme and then obtaining access to the data your link contains. Sending something like app://products/1 is not harmful, but sending tokens is a security concern.

当操作系统在打开链接时有两个或多个应用可供选择时,Android 会向用户显示 Disambiguation 对话框 并要求他们选择使用哪个应用来打开链接。然而在 iOS 上,操作系统会为你做出选择,所以用户会很高兴地不知道。Apple 已在更高版本的 iOS 版本 (iOS 11) 中采取措施解决此问题,并制定了先到先得的原则,尽管此漏洞仍可能以不同方式被利用,你可以阅读有关 此处 的更多信息。使用 通用链接 将允许在 iOS 中安全地链接到应用内的内容。

¥When the operating system has two or more applications to choose from when opening a link, Android will show the user a Disambiguation dialog and ask them to choose which application to use to open the link. On iOS however, the operating system will make the choice for you, so the user will be blissfully unaware. Apple has made steps to address this issue in later iOS versions (iOS 11) where they instituted a first-come-first-served principle, although this vulnerability could still be exploited in different ways which you can read more about here. Using universal links will allow linking to content within your app securely in iOS.

OAuth2 和重定向

¥OAuth2 and Redirects

OAuth2 身份验证协议如今非常流行,被誉为最完整、最安全的协议。OpenID Connect 协议也是基于此。在 OAuth2 中,要求用户通过第三方进行身份验证。成功完成后,该第三方将重定向回请求应用,并提供验证码,该验证码可以兑换为 JWT(JSON 网络令牌)。JWT 是一种开放标准,用于在网络各方之间安全传输信息。

¥The OAuth2 authentication protocol is incredibly popular nowadays, prided as the most complete and secure protocol around. The OpenID Connect protocol is also based on this. In OAuth2, the user is asked to authenticate via a third party. On successful completion, this third party redirects back to the requesting application with a verification code which can be exchanged for a JWT — a JSON Web Token. JWT is an open standard for securely transmitting information between parties on the web.

在网络上,此重定向步骤是安全的,因为网络上的 URL 保证是唯一的。对于应用来说情况并非如此,因为如前所述,没有集中的方法来注册 URL 方案!为了解决这个安全问题,必须以 PKCE 的形式添加额外的检查。

¥On the web, this redirect step is secure, because URLs on the web are guaranteed to be unique. This is not true for apps because, as mentioned earlier, there is no centralized method of registering URL schemes! In order to address this security concern, an additional check must be added in the form of PKCE.

PKCE,发音为“Pixy”,代表密钥代码交换证明,是 OAuth 2 规范的扩展。这涉及添加额外的安全层,以验证身份验证和令牌交换请求是否来自同一客户端。PKCE 使用 SHA 256 加密哈希算法。SHA 256 为任何大小的文本或文件创建唯一的“签名”,但它是:

¥PKCE, pronounced “Pixy” stands for Proof of Key Code Exchange, and is an extension to the OAuth 2 spec. This involves adding an additional layer of security which verifies that the authentication and token exchange requests come from the same client. PKCE uses the SHA 256 Cryptographic Hash Algorithm. SHA 256 creates a unique “signature” for a text or file of any size, but it is:

  • 无论输入文件如何,长度始终相同

    ¥Always the same length regardless of the input file

  • 保证对于相同的输入始终产生相同的结果

    ¥Guaranteed to always produce the same result for the same input

  • 一种方法(也就是说,你无法对其进行逆向工程以揭示原始输入)

    ¥One way (that is, you can’t reverse engineer it to reveal the original input)

现在你有两个值:

¥Now you have two values:

  • code_verifier - 客户端生成的大随机字符串

    ¥code_verifier - a large random string generated by the client

  • code_challenge - code_verifier 的 SHA 256

    ¥code_challenge - the SHA 256 of the code_verifier

在初始 /authorize 请求期间,客户端还为其保留在内存中的 code_verifier 发送 code_challenge。授权请求正确返回后,客户端还会发送用于生成 code_challengecode_verifier。然后,IDP 将计算 code_challenge,查看它是否与第一个 /authorize 请求中设置的内容匹配,并且仅在值匹配时才发送访问令牌。

¥During the initial /authorize request, the client also sends the code_challenge for the code_verifier it keeps in memory. After the authorize request has returned correctly, the client also sends the code_verifier that was used to generate the code_challenge. The IDP will then calculate the code_challenge, see if it matches what was set on the very first /authorize request, and only send the access token if the values match.

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

¥This guarantees that only the application that triggered the initial authorization flow would be able to successfully exchange the verification code for a JWT. So even if a malicious application gets access to the verification code, it will be useless on its own. To see this in action, check out this example.

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

¥A library to consider for native OAuth is react-native-app-auth. React-native-app-auth is an SDK for communicating with OAuth2 providers. It wraps the native AppAuth-iOS and AppAuth-Android libraries and can support PKCE.

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

¥React-native-app-auth can support PKCE only if your Identity Provider supports it.

OAuth2 with PKCE

网络安全

¥Network Security

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

¥Your APIs should always use SSL encryption. SSL encryption protects against the requested data being read in plain text between when it leaves the server and before it reaches the client. You’ll know the endpoint is secure, because it starts with https:// instead of http://.

SSL 固定

¥SSL Pinning

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

¥Using https endpoints could still leave your data vulnerable to interception. With https, the client will only trust the server if it can provide a valid certificate that is signed by a trusted Certificate Authority that is pre-installed on the client. An attacker could take advantage of this by installing a malicious root CA certificate to the user’s device, so the client would trust all certificates that are signed by the attacker. Thus, relying on certificates alone could still leave you vulnerable to a man-in-the-middle attack.

SSL pinning 是一种可以在客户端使用的技术来避免这种攻击。它的工作原理是在开发过程中向客户端嵌入(或固定)受信任的证书列表,以便仅接受使用受信任的证书之一签名的请求,而不会接受任何自签名的证书。

¥SSL pinning is a technique that can be used on the client side to avoid this attack. It works by embedding (or pinning) a list of trusted certificates to the client during development, so that only the requests signed with one of the trusted certificates will be accepted, and any self-signed certificates will not be.

使用 SSL 固定时,你应该注意证书过期。证书每 1-2 年过期一次,一旦过期,就需要在应用和服务器上进行更新。一旦服务器上的证书更新,任何嵌入旧证书的应用将停止工作。

¥When using SSL pinning, you should be mindful of certificate expiry. Certificates expire every 1-2 years and when one does, it’ll need to be updated in the app as well as on the server. As soon as the certificate on the server has been updated, any apps with the old certificate embedded in them will cease to work.

概括

¥Summary

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

¥There is no bulletproof way to handle security, but with conscious effort and diligence, it is possible to significantly reduce the likelihood of a security breach in your application. Invest in security proportional to the sensitivity of the data stored in your application, the number of users, and the damage a hacker could do when gaining access to their account. And remember: it’s significantly harder to access information that was never requested in the first place.