【问题标题】:PUT file to S3 with presigned URL使用预签名 URL 将文件 PUT 到 S3
【发布时间】:2012-04-23 10:03:12
【问题描述】:

我整晚都在使用 Amazon S3 预签名 URL 来尝试 PUT 文件。我在 java 代码中生成预签名的 URL。

    AWSCredentials credentials = new BasicAWSCredentials( accessKey, secretKey );
    client = new AmazonS3Client( credentials );
    GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest( bucketName, "myfilename", HttpMethod.PUT);
    request.setExpiration( new Date( System.currentTimeMillis() + (120 * 60 * 1000) ));
    return client.generatePresignedUrl( request ).toString();

然后我想使用生成的预签名 URL 来使用 curl 放置文件。

curl -v -H "content-type:image/jpg" -T mypicture.jpg https://mybucket.s3.amazonaws.com/myfilename?Expires=1334126943&AWSAccessKeyId=<accessKey>&Signature=<generatedSignature>

我认为,就像 GET 一样,这可以在不公开的存储桶上工作(这就是预签名的重点,对吗?)好吧,每次尝试我都拒绝访问。最后出于沮丧,我更改了存储桶的权限以允许每个人都写入。当然,然后预签名的 URL 起作用了。我迅速从存储桶中删除了 EVERYONE 权限。现在,我无权删除通过我自己的自预签名 URL 上传到我的存储桶中的项目。我see now 我可能应该在我上传的内容上放一个 x-amz-acl 标头。我怀疑在我做对之前我会创建更多不可删除的对象。

这引出了几个问题:

  • 如何使用 PUT 和生成的预签名 URL 通过 curl 上传?
  • 如何删除上传的文件和我创建的用于测试它的存储桶?

最终目标是手机将使用这个预签名的 URL 来 PUT 图像。我试图让它卷曲作为概念证明。

更新:我在amazon forums 上提出了一个问题。如果那里提供了答案,我会把它作为答案放在这里。

【问题讨论】:

  • 亚马逊论坛一如既往地有帮助(意味着我的问题没有被窥视)所以我最终不得不通过上传到我的应用程序然后让我的应用程序使用 java api 发送来解决这个问题图像到 S3。
  • 您是否有理由不使用预签名的 POST?也许他们会更好地工作,如果没有其他原因,他们可能已经在 SDK 中进行了更多的测试,等等。我当然能够使用亚马逊的 ruby​​ gem 在预签名的帖子中设置内容类型。
  • 他们的文档建议 PUT 作为放置内容的最佳方式。尽管如此,我确实花了一些时间试图让 POST 工作,但它也没有工作。同样,我认为问题在于正在设置的内容类型,也许它是 java SDK 特有的。一旦我得到了那里的内容(以任何方式),我的预签名 GET 工作得很好。我试过 SDK 1.3.3 和 1.3.6。我知道它们现在是 1.3.8,但发行说明没有表明与此问题相关的任何更改。
  • 建议:切换到基于标头的签名以使用 curl 放置您的文件,如 stackoverflow.com/a/59295183/3784642 中所述

标签: java curl amazon-s3


【解决方案1】:

也遇到了这个问题。我们已经在跟踪文件何时上传到后端,因此我们的解决方法是在客户端使用 Rails 应用程序通过调用 copy_from 上传文件后设置内容类型。

【讨论】:

    【解决方案2】:

    这确实有点令人费解,我认为这是AWS SDK for Java 中的一个错误(见下文) - 但首先,以下curl 命令将上传您的文件(假设更新的 pre-当然是签名的 URL):

    curl -v -T mypicture.jpg https://mybucket.s3.amazonaws.com/myfilename?Expires=1334126943&AWSAccessKeyId=<accessKey>&Signature=<generatedSignature>
    

    也就是说,我已经排除了Content type 标头,结果会产生application/octet-stream(或binary/octet-stream),这显然是不希望的;因此,需要进一步挖掘。

    背景/分析

    Amazon S3 的 PUT(和 DELETE 以及 HEAD)请求的预签名 URL 原则上是已知的,在本网站上的相关问题中至少可以证明这一点(例如,请参阅我对 Upload to s3 with curl using pre-signed URL (getting 403) 的回答)。

    便利的Query String Request Authentication Alternative 被记录为使用以下说明查询字符串请求身份验证方法的伪语法

    StringToSign = HTTP-VERB + "\n" +
        Content-MD5 + "\n" +
        Content-Type + "\n" +
        Expires + "\n" +
        CanonicalizedAmzHeaders +
        CanonicalizedResource;    
    

    它确实包含 Content-Type 标头,并且(正如您已经发现的那样)这是某些记录案例中缺少的部分,请参见例如AWS 团队对 GetPreSignedURL with PUT request 的回复,添加后会生成一个有效的预签名 URL。

    这很容易通过AWS SDK for .NET 实现,它提供了方便的方法GetPreSignedUrlRequest.WithContentType 来做到这一点:

    为此请求设置 ContentType 属性。该属性默认 到“二进制/八位字节流”,但如果你需要别的东西,你可以 设置此属性。

    因此,如下扩展相应的示例Upload an Object Using Pre-Signed URL - AWS SDK for .NET 会产生一个具有内容类型的工作预签名 URL,可以按预期通过 curl 上传(即完全按照您的尝试):

        // ...
        GetPreSignedUrlRequest request = new GetPreSignedUrlRequest();
        // ...
        request.WithContentType("image/jpg");
        // ...
    

    现在,人们想以类似的方式扩展语义相同的样本Upload an Object Using Pre-Signed URL - AWS SDK for Java,但是(正如您已经发现的那样),没有专门的方法来实现这一点。不过,这可能只是一种缺乏便利的方法,最终可以通过addRequestParameter()setResponseHeaders() 实现,例如:

      // ...
      request.setExpiration( new Date( System.currentTimeMillis() + (120 * 60 * 1000) ));
      request.addRequestParameter("content-type", "image/jpg");
      return client.generatePresignedUrl( request ).toString();
      // ...
    

    但是,这两种方法的文档都提出了其他用途,而且它确实不起作用,即它们总是产生相同的签名,无论哪种内容类型都是这样设置的(如果有的话)。

    对 SDK 的进一步调试表明,它们都提供了一种语义相似的核心方法来根据上面引用的 伪语法 计算查询字符串身份验证,请参阅 buildSigningString() for .NET 和 @987654334 @ 代表 Java。

    但Java版本中的相应代码将所有有趣的headers添加到一个列表中,然后对它们进行排序,其中“Interesting”定义为Content-MD5、Content-Type、Date , 而 x-amz- 实际上从未执行过,因为确实没有任何方法可以以某种方式提供这些标头,这些标头仅适用于 DefaultRequest 类而不是用于初始化前者的 GeneratePresignedUrlRequest 类,这用作依次计算签名的输入,参见受保护方法createRequest()

    有趣/值得注意的是,在 .NET 和 Java 中计算查询字符串身份验证的两种方法是由 headerparameter 源的几乎相反的组合构成它们的输入在调用堆栈上,这可能暗示 Java 错误的原因,但显然这也可能很难破译,即内部架构当然可能会有很大差异。

    初步结论

    这有两个角度:

    • AWS SDK for Java 肯定缺乏设置内容类型的便捷方法,这可能是一种比较少见的情况,但在其他 AWS 开发工具包中也有相应的明显用例 - 鉴于它在 AWS 中的广泛使用,这令人惊讶相关的后端服务。
    • 无论如何,与 .NET 版本相比,Query String Request Authentication 的实现方式似乎有些可疑 - 再次令人惊讶,因为它是一个核心功能,但是,这仍在 S3 模型/命名空间内,因此可能仅在上述各个用例中需要。

    总之,解决此问题的唯一合理方法是更新 SDK,因此需要提交错误报告 - 显然,也可以复制/扩展 SDK 功能以单独解决这种特殊情况(理想情况下以某种方式允许提交对aws-sdk-for-java project 的拉取请求),但是以兼容和可维护的方式来实现这一点似乎有点棘手,因此最好由 SDK 维护人员自己完成。

    【讨论】:

    • 从 1.11.8 版开始(我还没有深入研究差异以查看是否/何时发生了变化) GeneratePresignedUrlRequest 扩展了 AmazonWebServiceRequest,它为所有用户提供了 putCustomRequestHeader(String key, String value) 方法您的自定义标题需求。这是如何添加签名算法在内部包含为“有趣”的标头。这似乎没有(很好?)记录在案。此外,您需要在标题名称中手动包含“x-amz-”...,以将用户定义的元数据添加到上传的对象。
    • 正如我刚刚了解到的那样,ContentType 需要与实际执行 PUT 请求的任何工具相匹配。在我的例子中,我使用的是 Ajax,直到我发现这一点:stackoverflow.com/a/2845487/2162886 似乎任何在 PreSignedURL 中设置内容类型的调用都会(自然)失败。事后诸葛亮就像他们所说的 20/20,因为这很可能是显而易见的,而且不言而喻,但我会很感激它说的! :)
    • javascript SDK 也有同样的问题。您必须手动提供 Content-Type 才能使预设 url 起作用。
    • Url 应该在 curl 命令中的双引号下,因为没有它,该命令将不起作用并且 aws 将返回一些错误。
    猜你喜欢
    • 2019-08-05
    • 2020-09-30
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    • 1970-01-01
    • 2020-10-23
    • 2018-12-27
    • 1970-01-01
    相关资源
    最近更新 更多