我能够使用文件选择器和常规的XMLHttpRequest
(用于测试S3设置)上传到S3,但是无法弄清楚如何使用cordova文件传输插件成功完成此操作。
我认为这与该插件未构造正确的可签名请求有关,或者与该给定的本地文件uri不相关。我已经尝试过使用从标头到uri类型的每个参数,但是文档并没有太大帮助,并且插件来源是bolognese。
请求需要签名匹配的字符串如下:
PUT
1391784394
x-amz-acl:public-read
/the-app/317fdf654f9e3299f238d97d39f10fb1
有什么想法,或者可能是可行的代码示例?
最佳答案
有点晚了,但是我花了几天时间来解决这个问题,以防万一其他人遇到问题,这就是如何使用AWS开发工具包的javascript版本上载图像来创建预签名URL的方法。
解决此问题的关键是从亚马逊返回的XML StringToSign
错误的SignatureDoesNotMatch
元素。就我而言,它看起来像这样:
<StringToSign>
PUT\n\nmultipart/form-data; boundary=+++++org.apache.cordova.formBoundary\n1481366396\n/bucketName/fileName.jpg
</StringToSign>
当您使用
aws-sdk
生成要上传到S3的预签名URL时,它将在内部基于您要发出的请求的各种元素构建一个字符串,然后使用您的AWS密钥为其创建SHA1哈希。该哈希是作为参数附加到URL的签名,当您收到SignatureDoesNotMatch
错误时,该签名不匹配。因此,您已经创建了预签名的URL,并将其传递给
cordova-plugin-file-transfer
以发出HTTP请求以上传文件。当该请求到达Amazon服务器时,服务器本身将根据请求标头等构建一个字符串,对其进行哈希处理并将该哈希与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
在客户端,我将这个预签名的URL与
cordova-plugin-file-transfer
一起使用,如下所示(我使用的是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
标头中添加了标题,这导致了签名字符串之间的差异。在与传递到上载方法的options对象相关的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
标头,则默认为多部分表单数据。好的,现在我们知道了问题的原因,这是一个非常简单的修复。在服务器端,我向传递给S3
ContentType
方法的params
对象添加了getSignedUrl
:var params = {
Bucket: 'bucketName',
Key: imageName,
Expires: 60,
ContentType: 'image/jpeg' // <---- content type added here
};
在客户端上,将
headers
对象添加到传递给options
的上载方法的cordova-plugin-file-transfer
中:var options = {
fileName: filename,
mimeType: 'image/jpeg',
chunkedMode: false,
httpMethod:'PUT',
encodeURI: false,
headers: { // <----- headers object added here
'Content-Type': 'image/jpeg',
}
};
嘿,presto!现在可以正常进行上传。