17

我整晚都在使用 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 上传到我的存储桶中的项目。我现在看到我可能应该在我上传的内容上放置一个 x-amz-acl 标头。我怀疑在我做对之前我会创建更多不可删除的对象。

这导致了几个问题:

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

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

更新:我在亚马逊论坛上问了一个问题。如果那里提供了答案,我会将其作为答案放在这里。

4

2 回答 2

33

这确实有点令人费解,我认为这是AWS SDK for Java中的一个错误(见下文) - 但首先,以下curl命令将上传您的文件(当然假设更新的预签名 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原则上是已知的,在本网站上的相关问题中至少可以证明这一点(例如,请参阅我对使用预签名的 curl 上传到 s3 的回答) URL(得到 403))。

便利的查询字符串请求身份验证替代方案被记录为使用以下伪语法来说明查询字符串请求身份验证方法

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

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

这很容易通过适用于 .NET 的 AWS 开发工具包实现,它提供了便捷的方法GetPreSignedUrlRequest.WithContentType来做到这一点:

设置此请求的 ContentType 属性。此属性默认为“binary/octet-stream”,但如果您需要其他内容,您可以设置此属性。

因此,扩展相应的示例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 可以发现,它们都提供了语义相似的核心方法来根据上面引用的伪语法计算查询字符串身份验证参阅.NET 的buildSigningString()和 Java 的 makeS3CanonicalString()。

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

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

初步结论

这有两个角度:

  • 适用于 Java 的 AWS 开发工具包肯定缺乏设置内容类型的便捷方法,这可能是一种比较少见,但在其他 AWS 开发工具包中也有相应的明显用例 - 鉴于它在 AWS 相关后端服务中的广泛使用,这令人惊讶.
  • 无论如何,与 .NET 版本相比,查询字符串请求身份验证的实现方式似乎有些可疑- 这再次令人惊讶,因为它是核心功能,但是,这仍然在 S3 模型中/命名空间,因此可能只有上面的各个用例才需要。

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

于 2012-04-22T16:47:25.917 回答
0

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

于 2012-09-25T21:40:39.470 回答