这确实有点令人费解,我认为这是一个错误适用于 Java 的 AWS 开发工具包 http://aws.amazon.com/sdkforjava/(见下文) - 但首先也是最重要的,以下curl http://curl.haxx.se/命令将这样上传您的文件(当然假设更新了预签名 URL):
curl -v -T mypicture.jpg https://mybucket.s3.amazonaws.com/myfilename?Expires=1334126943&AWSAccessKeyId=<accessKey>&Signature=<generatedSignature>
也就是说,我已经排除了Content type
标头,产生application/octet-stream
(or binary/octet-stream
) 结果,这显然是不希望的;因此,需要进一步挖掘。
背景/分析
PUT(以及 DELETE 和 HEAD)请求的预签名 URL亚马逊S3 http://aws.amazon.com/s3/众所周知,原则上是有效的,而不是在本网站上的相关问题中最不明显的证据(例如,参见我的回答)使用预签名 URL 通过curl 上传到 s3(获取 403) https://stackoverflow.com/a/9085141/45773).
便利的查询字符串请求身份验证替代方案 http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth已记录使用以下内容说明查询字符串请求身份验证方法的伪语法:
StringToSign = HTTP-VERB + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Expires + "\n" +
CanonicalizedAmzHeaders +
CanonicalizedResource;
它确实包括Content-Type
标头,并且(正如您已经发现的那样)这是某些记录案例中缺失的部分,请参见例如AWS 团队的回应带有 PUT 请求的 GetPreSignedURL https://forums.aws.amazon.com/message.jspa?messageID=244858#244858,添加后会生成一个有效的预签名 URL。
这很容易实现适用于 .NET 的 AWS 开发工具包 http://aws.amazon.com/sdkfornet/确实,这提供了方便的方法GetPreSignedUrlRequest.WithContentType http://docs.amazonwebservices.com/sdkfornet/latest/apidocs/html/M_Amazon_S3_Model_GetPreSignedUrlRequest_WithContentType.htm为此:
设置此请求的 ContentType 属性。该属性默认
到“二进制/八位字节流”,但如果您需要其他东西,您可以
设置该属性。
因此,扩展相应的样本使用预签名 URL 上传对象 - 适用于 .NET 的 AWS 开发工具包 http://docs.amazonwebservices.com/AmazonS3/latest/dev/UploadObjectPreSignedURLDotNetSDK.html如下生成一个具有内容类型的工作预签名 URL,可以通过以下方式上传curl正如预期的那样(即完全按照您的尝试):
// ...
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest();
// ...
request.WithContentType("image/jpg");
// ...
现在,我们想扩展语义相同的样本使用预签名 URL 上传对象 - AWS SDK for Java http://docs.amazonwebservices.com/AmazonS3/latest/dev/PresignedUrlUploadObjectDotNetSDK.html以类似的方式,但是(正如您也已经发现的那样),没有专门的方法来实现这一点。但这可能只是一种缺乏便利的方法,可以通过以下方式实现添加请求参数() http://docs.amazonwebservices.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/model/GeneratePresignedUrlRequest.html#addRequestParameter%28java.lang.String,%20java.lang.String%29 or 设置响应头() http://docs.amazonwebservices.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/model/GeneratePresignedUrlRequest.html#setResponseHeaders%28com.amazonaws.services.s3.model.ResponseHeaderOverrides%29最终,例如:
// ...
request.setExpiration( new Date( System.currentTimeMillis() + (120 * 60 * 1000) ));
request.addRequestParameter("content-type", "image/jpg");
return client.generatePresignedUrl( request ).toString();
// ...
然而,这两种方法的文档都表明了其他目的,并且它确实不起作用,即它们总是产生相同的签名,无论像这样设置哪种内容类型(如果有)。
进一步调试 SDK 发现,两者都提供了语义相似的核心方法来根据伪语法上面提到的,参见构建签名字符串() https://github.com/amazonwebservices/aws-sdk-for-net/blob/master/Amazon.S3/AmazonS3Client.cs#L6173对于 .NET 和makeS3CanonicalString() https://github.com/amazonwebservices/aws-sdk-for-java/blob/master/src/main/java/com/amazonaws/services/s3/internal/RestUtils.java#L57对于Java。
但Java版本中的相应代码为将所有感兴趣的标题添加到列表中,然后对它们进行排序, where “有趣”定义为 Content-MD5、Content-Type、Date 和 x-amz-事实上从未执行过,因为确实没有方法以某种方式提供这些标头,这些标头仅适用于类默认请求 http://docs.amazonwebservices.com/AWSJavaSDK/latest/javadoc/com/amazonaws/DefaultRequest.html而不是阶级生成预签名 URL 请求 http://docs.amazonwebservices.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/model/GeneratePresignedUrlRequest.html用于初始化前者,依次作为计算签名的输入,参见protected方法创建请求() https://github.com/amazonwebservices/aws-sdk-for-java/blob/master/src/main/java/com/amazonaws/services/s3/AmazonS3Client.java#L2593.
有趣/值得注意的是,在 .NET 与 Java 中计算查询字符串身份验证的两种方法的输入几乎是相反的组合:header vs. 范围调用堆栈上的源代码,这可能暗示 Java bug 的原因,但显然这也可能很难破译,即内部架构当然可能有很大不同。
初步结论
对此有两个角度:
- AWS SDK for Java 肯定缺乏设置内容类型的便捷方法,这可能是一种相对罕见的方法,但在其他 AWS SDK 中也有明显的用例 - 这是令人惊讶的,因为它在 AWS 相关后端服务中广泛使用。
- 无论如何,这种方式似乎有些可疑查询字符串请求身份验证例如,与 .NET 版本相比,这再次令人惊讶,因为它是核心功能,但是,它仍然在 S3 模型/命名空间内,因此可能只有上面的各个用例需要。
总之,解决此问题的唯一合理方法是更新 SDK,因此需要提交错误报告 - 显然,人们也可以复制/扩展 SDK 功能以单独解决这种特殊情况(理想情况下以允许提交的方式)的拉取请求aws-sdk-for-java 项目 https://github.com/amazonwebservices/aws-sdk-for-java),但以兼容且可维护的方式做到这一点似乎有点棘手,因此最好由 SDK 维护人员自己完成。