使用预签名URL将文件压缩到S3

dig*_*oel 17 java curl amazon-s3

我整晚都在尝试使用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();
Run Code Online (Sandbox Code Playgroud)

然后我想使用生成的预签名URL来使用curl PUT文件.

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

我认为,就像GET一样,这可以在一个非公开的存储桶上工作(那是预先签名的,对吗?)好吧,我在每次尝试时都被拒绝访问.最后,出于沮丧,我改变了桶的许可,允许每个人写.当然,预先签名的URL有效.我迅速从桶中删除了每个人的权限.现在,我无权删除通过我自己预先签名的URL上传到我的存储桶中的项目.我现在看到我可能应该在我上传的内容上放置一个x-amz-acl标题.我怀疑在我做对话之前,我会创建几个不可修复的对象.

这导致了一些问题:

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

最终目标是移动电话将使用此预先指定的URL来PUT图像.我试图让它成为curl作为概念的证明.

更新:我在亚马逊论坛上提出了一个问题.如果在那里提供答案,我会在此处作为答案.

Ste*_*pel 32

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

curl -v -T mypicture.jpg https://mybucket.s3.amazonaws.com/myfilename?Expires=1334126943&AWSAccessKeyId=<accessKey>&Signature=<generatedSignature>
Run Code Online (Sandbox Code Playgroud)

也就是说,我已经排除了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;    
Run Code Online (Sandbox Code Playgroud)

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

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

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

因此,扩展相应的示例使用预签名URL上传对象 - AWS SDK for .NET如下所示产生一个带有内容类型的工作预签名URL,可以按预期通过curl上传(即完全按照您的尝试):

    // ...
    GetPreSignedUrlRequest request = new GetPreSignedUrlRequest();
    // ...
    request.WithContentType("image/jpg");
    // ...
Run Code Online (Sandbox Code Playgroud)

现在,人们希望以类似的方式扩展语义上相同的示例使用预签名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();
  // ...
Run Code Online (Sandbox Code Playgroud)

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

进一步调试成的SDK揭示,这两个提供语义相似芯方法根据以计算查询串认证伪语法上述参考,见buildSigningString()用于.NET和makeS3CanonicalString()为Java.

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

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

初步结论

这有两个角度:

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

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

  • 从版本1.11.8开始(我没有深入研究差异,看看是否/何时可能发生了变化)GeneratePresignedUrlRequest扩展了AmazonWebServiceRequest,它为您提供了所有自定义标头需求的方法putCustomRequestHeader(String key,String value).这是如何在内部添加签名算法包含的"有趣"标题.这似乎没有(嗯?)记录.此外,您需要在标题名称中手动包含"x-amz - "...以将用户定义的元数据添加到上载的对象. (2认同)