在 iOS 客户端和后端服务器上管理对 AWS 服务的访问

Managing access to AWS services on iOS clients vs on backend servers

在设计将与 AWS 交互的 iOS 应用程序(例如 S3CloudFront 等)时,管理访问权限的优缺点是什么这些服务在客户端还是在服务器?

"managing the access",我的意思是将私人内容上传到 S3,通过 Cloudfront 下载私人内容。

当然,无论哪一方处理访问都需要存储AWS访问密钥和访问秘密。安全是问题之一。

我对这种设计选择对任一实现的性能和灵活性的影响同样感兴趣。

最后,是否有关于实现客户端和服务器直接与 AWS 交互的混合方法的争论,或者实现通常与其中一个而不是两者一起使用?

安全性是我在对用户进行身份验证后将 all/most AWS 服务身份验证放在后端的主要原因。

另一个考虑因素是考虑到他们的审批流程,在 Apple Store 上刷新您的应用程序所需的时间。根据苹果商店的排队情况,您可能需要几天时间才能将更改推送到您的应用程序;可以随意更改 AWS 后端。

此外,在设计与 AWS 服务交互的应用程序时,我始终假设传输的任何内容都可能受到损害,并且很可能会被解构您的调用并重建自己的调用以满足他们需求的人使用。

(例如,在启动上传图像然后应用过滤器的照片娱乐应用程序后不久,我们注意到来自同一 IP 的应用程序中不存在的过滤器 ID 的日志条目。它们没有成功,因为他们没有通过身份验证。)

希望对您有所帮助。

虽然在很多情况下ios您通常可能想以任何一种方式执行此操作,但几乎没有任何情况下您会想直接从客户端执行此操作,因为您提到了 ios :

通过服务器端将数据上传到 AWS 的优点:

  1. 安全

    正如在另一个答案中已经提到的那样,如果不法分子和黑客试图破坏内容,最初拥有经过身份验证的请求将为您省去很多麻烦。如果数据是私有的并且您真正致力于保护隐私,那么如果系统经过身份验证,任何数据泄漏都将更容易防止。

  2. 速率限制、用户配额等

    经过身份验证的系统的额外优势是您可以对来自特定来源的请求进行速率限制,例如用户、组、IP 等(如果您打算围绕同一系统架构构建多个应用程序,则应用程序级别配额)。当您直接在客户端工作时,构建这种智能并不那么容易。

  3. 审计追踪

    如果您需要跟踪谁上传了什么、何时、从何处上传以及更多此类信息,如果您直接在您的服务器上获得初始请求,这将再次更容易跟踪。

  4. 失败时的异常处理

    很有可能会出现您无法轻易预测到的失败,或者在 QA 测试期间遗漏了一个关键错误。处理这些服务器端效率更高,因为它在您的控制之下。当客户端出现任何此类问题时,您将受制于能够升级应用程序的客户。如果您正在处理此服务器端,则可以很容易地对许多此类错误进行额外检查 placed/deployed,从而限制错误的范围。

  5. 上线时间

    同样,如其他答案中所述,您的更新可能需要一段时间才能获得批准。这大大降低了您对关键问题的响应能力,并且在严重问题(数据 leak/privacy 泄露)导致重大损失(财务/用户信任/负面评级等)的情况下很难缓解

我认为您希望将数据直接从客户端上传到 AWS 的唯一情况是

  • 上传大量数据,非常非常频繁,没有直接处理。

    如果上传一次需要一定的带宽和网络资源,上传两次需要双倍的资源(一次来自client --> server,然后来自server --> AWS)。因此,如果您频繁上传大量数据(每天想想 TB),那么您最终只会浪费大量资源,只是将数据从一个点复制到另一个点。在这种情况下,将数据直接推送到 S3 是有意义的。但要使这种方法奏效,您节省的成本应该足够多,足以克服对安全和隐私的担忧,而对于大多数应用程序而言,情况并非如此。

  • 你在一个有围墙的花园里

    基本上,该应用程序仅适用于某些预先确定的用户,该应用程序根本不适用于其他任何人(假设您正在开发此应用程序供公司内部使用)。从本质上讲,这意味着对最终用户使用您的应用程序的动机有 100% 的信心。


编辑:OP 在评论中提问

How about server providing signed URL/cookies, and client uses these to upload to S3 or download from Cloudfront. Client still directly interacts with AWS but requires permissions controlled by server.

乍一看,我觉得这很可行。这个blog post provides many use cases (like providing wildcard signed urls for reading) around using signed urls (though the examples are in .NET) and more information is available at AWS docs.

由于您要处理签名服务器端,您可以轻松处理我之前在 post 中提到的每个要点(速率限制、用户配额、审计跟踪等都是可行的,因为请求最初将转到服务器)。正如 this answer 提到的那样,

Signing URLs helps control who can access a given file and how long they can access it for.

总的来说,这应该适用于相当多的用例。

除了其他好的答案之外,我还想补充一点:与网络应用程序不同,您不能期望所有用户都拥有您的应用程序的最新版本。这意味着您的应用程序的任何版本调用的任何服务器 url 原则上必须永远保持活动状态。这意味着如果你想改变你的服务器基础设施(例如从 AWS 迁移到其他云主机)那么你不能因为即使你使用新的 urls 制作应用程序的更新版本,那么仍然会有一些未更新的应用程序调用旧的 urls。

你当然可以选择在应用中做一个"forced update"机制,在更新之前你不能使用它(这在多人游戏中很常见,但不是很多其他地方)或者只是不关心你的应用程序旧版本的少数用户,你让他们的生活变得悲惨(情节扭曲 - 他们可能会卡在你的应用程序的旧版本上,因为他们的设备无法升级到最新 iOS 版本)。

但 IMO 更好的解决方案是将 AWS url 隐藏在您自己的服务器后面,这样您就永远不会 运行 遇到这个问题。这是一个你真的不应该泄露给客户端的实现细节。

出于安全原因,将您的密钥保存在无法被篡改的地方很重要:这通常意味着将它们留在服务器上。

以这种方式思考您的密钥:它们授予对您组织资源的访问权限。通过将您的密钥放在移动设备上,密钥被盗会影响您在组织级别的资源。相反,在移动设备上使用用户级身份验证,通过服务器上的代理授予对 AWS 资源的访问权限。这样,用户级凭据的丢失不会导致组织级丢失。撤销用户级凭证更容易。

您还提到了上传到 S3。 AWS 有一个很好的设施,称为预签名-post,您的服务器会在其中生成一次性上传凭证,您的移动设备可以使用该凭证将数据上传到 S3 ...而无需通过您的服务器代理数据。

# ruby
presigned_post = bucket.presigned_post(key: key, success_action_status: 201, acl: :public_read)