有点晚了,但我只是花了几天的时间来解决这个问题,所以如果其他人遇到问题,这就是如何使用 AWS 开发工具包的 javascript 版本来上传图像以创建预签名 URL。
解决问题的关键在于从 Amazon 返回的 XML SignatureDoesNotMatch 错误的 StringToSign 元素。就我而言,它看起来像这样:
<StringToSign>
PUT\n\nmultipart/form-data; boundary=+++++org.apache.cordova.formBoundary\n1481366396\n/bucketName/fileName.jpg
</StringToSign>
当您使用 aws-sdk 生成预签名 URL 以上传到 S3 时,在内部它将根据您要发出的请求的各种元素构建一个字符串,然后使用您的 AWS 密码创建它的 SHA1 哈希。此哈希是作为参数附加到 URL 的签名,当您收到 SignatureDoesNotMatch 错误时,该签名不匹配。
因此,您已经创建了预签名 URL,并将其传递给 cordova-plugin-file-transfer 以发出上传文件的 HTTP 请求。当该请求到达亚马逊的服务器时,服务器本身将根据请求标头等构建一个字符串,对其进行散列并将该散列与 URL 上的签名进行比较。如果哈希值不匹配,则返回可怕的...
The request signature we calculated does not match the signature you provided. Check your key and signing method.
我上面提到的StringToSign 元素的内容是服务器构建和散列以与预签名 URL 上的签名进行比较的字符串。所以为了避免报错,你需要确保aws-sdk构建的字符串和服务器构建的字符串是一样的。
经过一番挖掘,我最终在aws-sdk 中找到了负责创建要散列的字符串的代码。它位于(截至版本 2.7.12):
node_modules/aws-sdk/lib/signers/s3.js
在第 168 行的底部有一个 sign 方法:
sign: function sign(secret, string) {
return AWS.util.crypto.hmac(secret, string, 'base64', 'sha1');
}
如果您将console.log 放在那里,string 就是您所追求的。一旦您将传递给此方法的string 与从亚马逊返回的错误消息中的StringToSign 的内容相同,天堂就会打开,您的文件将毫不费力地流入您的存储桶。
在我运行 node.js 的服务器上,我最初创建了这样的预签名 URL:
var AWS = require('aws-sdk');
var s3 = new AWS.S3(options = {
endpoint: 'https://s3-eu-west-1.amazonaws.com',
accessKeyId: "ACCESS_KEY",
secretAccessKey: "SECRET_KEY"
});
var params = {
Bucket: 'bucketName',
Key: imageName,
Expires: 60
};
var signedUrl = s3.getSignedUrl('putObject', params);
//return signedUrl
这产生了一个像这样的签名字符串,类似于 OP:
PUT
1481366396
/bucketName/fileName.jpg
在客户端,我使用这个带有 cordova-plugin-file-transfer 的预签名 URL,就像这样(我使用的是 Ionic 2,所以插件被包装在它们的原生包装器中):
let success = (result: any) : void => {
console.log("upload success");
}
let failed = (err: any) : void => {
let code = err.code;
alert("upload error - " + code);
}
let ft = new Transfer();
var options = {
fileName: filename,
mimeType: 'image/jpeg',
chunkedMode: false,
httpMethod:'PUT',
encodeURI: false,
};
ft.upload(localDataURI, presignedUrlFromServer, options, false)
.then((result: any) => {
success(result);
}).catch((error: any) => {
failed(error);
});
运行代码产生签名不匹配错误,<StringToSign> 元素中的字符串如下所示:
PUT
multipart/form-data; boundary=+++++org.apache.cordova.formBoundary
1481366396
/bucketName/fileName.jpg
所以我们可以看到cordova-plugin-file-transfer 添加了自己的Content-Type 标头,这导致了签名字符串的差异。在与传递到上传方法的选项对象相关的docs 中,它说:
headers: A map of header name/header values. Use an array to specify more than one value. On iOS, FireOS, and Android, if a header named Content-Type is present, multipart form data will NOT be used. (Object)
所以基本上,如果没有设置Content-Type 标头,它将默认为多部分表单数据。
好的,现在我们知道了问题的原因,这是一个非常简单的解决方法。在服务器端,我将ContentType 添加到传递给S3 getSignedUrl 方法的params 对象:
var params = {
Bucket: 'bucketName',
Key: imageName,
Expires: 60,
ContentType: 'image/jpeg' // <---- content type added here
};
并在客户端上将headers 对象添加到传递给cordova-plugin-file-transfer 的上传方法的options:
var options = {
fileName: filename,
mimeType: 'image/jpeg',
chunkedMode: false,
httpMethod:'PUT',
encodeURI: false,
headers: { // <----- headers object added here
'Content-Type': 'image/jpeg',
}
};
嘿,快!上传现在按预期工作。