将文件放入具有预签名 URL 的 S3

2022-09-03 00:20:28

我整晚都在玩Amazon S3预签名URL,试图放置文件。我用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来放置图像。我试图让它以卷曲作为概念证明。

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


答案 1

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

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

也就是说,我已经排除了标题,它产生(或)结果,这显然是不希望的;因此,进一步的挖掘已经下令。Content typeapplication/octet-streambinary/octet-stream

背景/分析

众所周知,对 Amazon S3 的 PUT(和 DELETE 以及 HEAD)请求的预签名 URL 原则上是有效的,尤其是在此站点的相关问题中得到了证明(例如,请参阅我对使用预签名 URL 使用 curl 上传到 s3 的回答(获取 403))。

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

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

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

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

设置此请求的 ContentType 属性。此属性默认为“二进制/八位字节流”,但如果需要其他内容,则可以设置此属性。

因此,按如下方式扩展相应的示例“使用预签名 URL 上传对象 - 适用于 .NET 的 AWS 开发工具包”将生成一个具有内容类型的工作预签名 URL,该 URL 可以按预期通过 curl 上传(即,完全符合您的尝试):

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

现在,人们希望以类似的方式扩展语义相同的示例“使用预签名 URL 上传对象 - 适用于 Java 的 AWS 开发工具包”,但是(正如您已经发现的那样),没有专用的方法可以实现这一点。这可能只是一种缺乏便利的方法,最终可以通过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版本中的相应代码将所有有趣的标头添加到列表中,然后对它们进行排序,其中“Interesting”被定义为Content-MD5,Content-Type,Date和x-amz-实际上从未执行过,因为确实没有办法以某种方式提供这些标头,这些标头仅适用于Class DefaultRequest,而不适用于Class GeneratePresignedUrlRequest。 用于初始化前者,前者用作依次计算签名的输入,请参阅受保护的方法 createRequest()。

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

初步结论

这有两个角度:

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

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


答案 2

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


推荐