如何在iOS中为URLRequest签名并从S3下载文件。

让我们讨论一下在iOS移动应用程序中从AWS S3下载文件。

AWS提供了可与我们的移动应用程序一起使用的一批服务。 所有这些都可以在开发人员控制台中通过单独的Mobile Hub进行控制。 而且,它具有iOS SDK,使开发人员的生活更加轻松。 实际上有时不是,但这是另一回事了。 通常,就可以直接使用它来获得结果,并拥有Apps中最流行和最常见的解决方案。

因此,如果您想以常规方式与S3通信并下载/上传文件,我强烈建议使用iOS的官方AWS开发工具包。

但是,如果我们有一些限制,又不想将AWSS3添加到我们的项目中,而只是下载一个文件一次,或者我们需要URLRequest并随其一起下载一些东西。

在这种情况下,我们有一些选择。 最简单的方法是在存储桶中公开所需文件。 对于所有具有正确链接的人,该文件都是可见的。 但这并不安全,而且这种方法非常取决于我们的需求和内容类型。 是的,我们仍然可以拥有一个独特且丑陋的链接,因此没有人会猜到它。 但是最好使用所有可能的解决方案来确保内容安全并让我们的用户感到安全,尤其是当我们可以从AWS盒子中直接获取内容时。

让我们创建签名的请求,以从我们的S3存储桶中获取非公开图像。
AWS使用Signature V4,因此我们将使用它。 但是,根据官方文档显示,如果旧区域在2014年1月10日之前创建,则仍然可以支持Signature V2。

首先,我们需要S3。 我建议在没有移动中心方面的帮助的情况下创建它。 如果您熟悉AWS和此过程,请跳过本节。


我假设您已经有AWS开发人员帐户。 或者您可以创建一个进行测试。 AWS有免费层,足以试验各种想法,学习甚至MVP。

我们需要从服务列表中选择S3服务。

现在,我们可以创建一个新的存储桶以进行测试。 我们将其称为downloadimagetestbucket 。 目前,我们可以保留所有默认设置。

我们有存储空间,所以我们上传测试图像。 保留所有默认设置,以确保它不是公开的。

我们需要AWS凭证来生成请求签名。 我们可以使用为我们的根帐户生成的访问密钥ID和密码。 但这不是一个好主意,特别是如果您是所有者。 这样的凭据将拥有对所有内容的完全访问权限。 我们可以将它们用于快速实验,但不能用于现实生活中的Apps。 我们将仅创建具有访问我们测试桶权限的IAM用户。

与以前一样,从具有所有服务列表的AWS控制台中选择IAM。 在“用户”标签中,我们可以添加一个新用户。

一个名叫downloadimagetestuser且具有程序访问权限的神话人物。

然后是下一个…下一个…下一个并创建。 不要忘记保存ID和机密。
我们的用户没有任何权限,他在我们的AWS中无能为力。 绝对没用的人。

让我们教他一些技巧。 我们需要添加访问S3的权限。

为此,我们将从“策略”选项卡中创建单独的策略。

我们可以使用可视化编辑器,但有时使用JSON,可以更快。 但是在这种情况下,我们应该知道我们在做什么; 否则它将无法正常工作甚至无法通过验证。

我们将仅授予来自S3的读取对象。 您可以在官方文档中找到更多操作。 另外,我们需要存储桶ARN仅允许访问它。 您可以选择S3存储桶并从信息中复制它。

JSON看起来像

  { 
“ Version”:“ 2012-10-17”,
“声明”:[
{
“ Sid”:“ 65487465138798”,
“效果”:“允许”,
“动作”:[
“ s3:GetObject”
],
“资源”:“ arn:aws:s3 ::: downloadimagetestbucket / *”
}
]
}

让我们将其命名为downloadimagetestbucketpolicy 。 您也可以添加一些描述。 然后创建它。 现在,我们应该回到创建的无用用户。 选择它,然后在权限选项卡中选择添加权限

选择直接附加现有策略,然后按策略名称过滤。 将测试策略附加到用户。

现在,有了此权限,我们就可以使用我们的用户了。


为了创建一个请求,我们将需要

  • 文件网址,我们可以通过从存储桶中选择文件来找到它。
  • IAM用户访问密钥ID和机密,我们在创建新用户时将其保存。
  • 当我们从S3,Amazon S3部分中所有区域的列表中选择存储桶时,存储桶区域,区域名称是可见的。 我们将需要像us-east-1这样的字符串。

官方文档中的一些信息。

  • 如何从S3获取对象。 链接
  • 关于身份验证请求。 链接
  • 如何计算签名。 链接

这个想法很简单。 我们有一些要求。 我们使用用户的机密,并通过请求传递用户的ID,从中计算出签名字符串。 AWS将使用此ID来查找用户,并将使用相同的机密来计算签名。 然后将其与请求中的签名进行比较。 如果相同的话,一切都会好的。

签名取决于HTTP标头,因此让我们在实际计算签名之前添加一些标头。

  1. 我们将需要一个十六进制编码的SHA256哈希字符串作为有效载荷; 我们下载了一个文件,因此将使用0字节的空数据。 将其放置在x-amz-content-sha256标头中。
  2. 在本例中,添加带有image/png字符串的Content-Type标头。
  3. Host字段,其中包含服务名称+地区+ amazonaws.com
    模板(serviceName).(region).amazonaws.com
    (serviceName).(region).amazonaws.com
    将看起来像s3.us-east1.amazonaws.com
  4. 字符串格式yyyyMMdd’T’HHmmss’Z’的 X-Amz-Date日期。 对于所有日期字符串,请使用GMT区域和en_US_POSIX语言环境。 将日期保存在某处; 我们将在几个地方需要相同的时间戳。
  5. 最后一个字段是auth Authorization 。 它包含签名算法,凭据,签名的标头和签名本身。
    模板AWS4-HMAC-SHA256 Credential=(requestCredentials) SignedHeaders=(signedHeaders) Signature=(signature)
    AWS4-HMAC-SHA256 Credential=(requestCredentials) SignedHeaders=(signedHeaders) Signature=(signature)
    看起来像AWS4-HMAC-SHA256 Credential=AXXXXF6YJEKB2NFZXXXX/20181009/us-east-1/s3/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=d3402ed5d4d46cea0b3c17e78c421a8afce0a58fd01f24dd77dfb06893613445

该字符串包含访问密钥ID和请求范围。
模板(accessKeyID)/(requestScope)

请求范围包含

  • 简短格式yyyyMMdd日期字符串。
  • 我们的地区。
  • 服务名称,在本例中为s3。
  • 还有一个终结器aws4_request

我们也将需要请求范围来计算签名。

范围模板(dateString)/(region)/(serviceName)/(terminator)

看起来像20181009/us-east-1/s3/aws4_request

完整模板(accessKeyID)/(dateString)/(reion)/(serviceName)/(terminator)

看起来像AXXXXF6YJEKB2NFZXXXX/20181009/us-east-1/s3/aws4_request

应该包含我们用于计算签名的所有标头。

我们需要请求中的所有HTTP字段名称,以不区分大小写的方式按字母顺序对它们进行排序,并使用分号分隔符进行枚举。 标头应小写。 我们将使用HTTP标头几次,并且每次应对其进行排序。

模板(header);(header)

看起来像content-type;host;x-amz-content-sha256;x-amz-date

正如我们在文档步骤中看到的,计算签名

  1. 创建规范的请求字符串
  2. 创建StringToSign字符串
  3. 计算签名

创建规范的请求字符串

每个部分都应换行\n

它包含了

  1. HTTP方法,本例中为GET
  2. URL编码的路径/downloadimagetestbucket/TestImage.png
  3. 然后是规范查询字符串,但是现在不需要,因此只需换行\n
  4. 规范标题字符串
    通常应按小写名称排序。 应该包含标题名称和值,并用新行\n分隔。 并且不应包含多余的空格。
    模板(headerName1):(headerValue1)\n(headerName2):(headerValue2)
    (headerName1):(headerValue1)\n(headerName2):(headerValue2)
    看起来像content-type:image/png\nhost:s3.us-east-1.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20181009T115731Z\n
  5. 带签名的标头字符串,与我们之前构造的相同。
  6. 请求负载的十六进制编码SHA256哈希字符串,也曾针对0字节的数据进行过计算。 我们可以重复使用它。

测试应用程序的最终规范请求字符串将类似于GET\n/downloadimagetestbucket/TestImage.png\n\ncontent-type:image/png\nhost:s3.us-east-1.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20181009T115731Z\n\ncontent-type;host;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

创建StringToSign字符串

对于规范的请求,每个部分都应使用新行\n

它包含了

  1. AWS签名算法AWS4-HMAC-SHA256
  2. ISO8601格式的日期yyyyMMdd’T’HHmmss’Z’,我们以前使用过。
  3. 之前也使用过的要求范围。 看起来像20181009/us-east-1/s3/aws4_request
  4. 来自我们构造的规范请求字符串的十六进制编码的SHA256字符串

模板AWS4-HMAC-SHA256\n(dateStringISO8601)\n(requestScope)\n(hexEncodedSHA256CanonicalRequestString)

最终StringToSign看起来像AWS4-HMAC-SHA256\n20181009T115731Z\n20181009/us-east-1/s3/aws4_request\n61c352d185e6349d274da84ec475138061572f59d6dbecfcfb7f12fd4c5ce36f

计算签名

正如在AWS使用HMAC-SHA256算法之前所看到的,我们可以使用CommonCrypto框架或任何第三方解决方案来使我们的生活变得更简单。

下一步是使用HMAC-SHA256使用先前生成的密钥对数据进行嵌套加密。

  1. 创建DateKey 。 使用密钥将AWS4(secretKey)格式的日期字符串以短格式yyyyMMdd加密
  2. 创建DateRegionKey 。 使用先前创建的密钥DateKey加密区域字符串。
  3. 创建DateRegionServiceKey 。 在本例中,使用创建的密钥DateRegionKey加密服务名称s3
  4. 创建SigningKey 。 使用创建的DateRegionServiceKey加密终止aws4_request aws4_request
  5. 创建最终的十六进制编码的签名字符串。 使用SigningKey加密在StringToSign之前创建的内容

就这样。 现在,我们拥有用于Authorization HTTP标头的所有内容。 我们可以从S3下载文件。

您可以检查演示项目。 这只是一个示例,建议不要按原样使用它,因为在某些地方使用过的力可以解开包装,但是您可以根据自己的特定需求以更安全的方式采用此解决方案。