cordova-plugin-file-transfer:如何使用签名 URL 将文件上传到 S3?

Mat*_*Way 1 javascript android amazon-s3 cordova ionic-framework

我可以使用文件选择器和常规文件XMLHttpRequest(我用来测试 S3 设置)上传到 S3,但无法弄清楚如何使用 cordova 文件传输插件成功完成此操作。

我相信这要么与插件没有构建正确的可签名请求有关,要么与给定的本地文件 uri 不喜欢有关。我尝试过使用从标头到 uri 类型的每个参数,但文档没有太大帮助,而且插件源是博洛尼亚语。

请求需要签名匹配的字符串如下:

PUT


1391784394
x-amz-acl:public-read
/the-app/317fdf654f9e3299f238d97d39f10fb1
Run Code Online (Sandbox Code Playgroud)

有什么想法,或者可能是一个工作代码示例?

小智 5

有点晚了,但我只是花了几天时间来解决这个问题,所以如果其他人遇到问题,这就是如何使用 AWS SDK 的 javascript 版本上传图像来创建预签名 URL。

解决问题的关键在于亚马逊返回的StringToSignXML错误元素。SignatureDoesNotMatch就我而言,它看起来像这样:

<StringToSign>
    PUT\n\nmultipart/form-data; boundary=+++++org.apache.cordova.formBoundary\n1481366396\n/bucketName/fileName.jpg
</StringToSign>
Run Code Online (Sandbox Code Playgroud)

当您使用aws-sdk生成预签名 URL 以上传到 S3 时,它会在内部根据您想要发出的请求的各种元素构建一个字符串,然后使用您的 AWS 密钥创建该字符串的 SHA1 哈希值。此哈希值是作为参数附加到 URL 的签名,当您收到错误时,该签名与不匹配的内容不同SignatureDoesNotMatch

您已经创建了预签名 URL,并将其传递给cordova-plugin-file-transferHTTP 请求来上传文件。当该请求到达 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
Run Code Online (Sandbox Code Playgroud)

这产生了一个像这样的签名字符串,类似于OP:

PUT


1481366396
/bucketName/fileName.jpg
Run Code Online (Sandbox Code Playgroud)

在客户端,我使用了这个预签名 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);
});
Run Code Online (Sandbox Code Playgroud)

运行代码会产生签名不匹配错误,<StringToSign>元素中的字符串如下所示:

PUT

multipart/form-data; boundary=+++++org.apache.cordova.formBoundary
1481366396
/bucketName/fileName.jpg
Run Code Online (Sandbox Code Playgroud)

所以我们可以看到它cordova-plugin-file-transfer添加了自己的Content-Type标头,这导致了签名字符串的差异。在与传递到上传方法的选项对象相关的文档中,它说:

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)
Run Code Online (Sandbox Code Playgroud)

所以基本上,如果没有Content-Type设置标题,它将默认为多部分表单数据。

好的,现在我们知道了问题的原因,这是一个非常简单的修复方法。在服务器端,我将 a 添加ContentTypeparams传递给 S3getSignedUrl方法的对象:

var params = {
    Bucket: 'bucketName',
    Key: imageName,
    Expires: 60,
    ContentType: 'image/jpeg' // <---- content type added here
};
Run Code Online (Sandbox Code Playgroud)

并在客户端上将一个headers对象添加到options传递给 的cordova-plugin-file-transferupload 方法中:

var options = {
    fileName: filename,
    mimeType: 'image/jpeg',
    chunkedMode: false,
    httpMethod:'PUT',
    encodeURI: false,
    headers: { // <----- headers object added here
        'Content-Type': 'image/jpeg',
    }
};
Run Code Online (Sandbox Code Playgroud)

嘿,快点!现在上传可以按预期进行。