使用签名URL上传到S3时获取403(禁止)

时间:2017-10-22 02:34:57

标签: javascript node.js amazon-web-services amazon-s3 axios

我正在尝试生成预签名的网址,然后通过浏览器将文件上传到S3。我的服务器端代码如下所示,它生成URL:

let s3 = new aws.S3({
  // for dev purposes
  accessKeyId: 'MY-ACCESS-KEY-ID',
  secretAccessKey: 'MY-SECRET-ACCESS-KEY'
});
let params = {
  Bucket: 'reqlist-user-storage',
  Key: req.body.fileName, 
  Expires: 60,
  ContentType: req.body.fileType,
  ACL: 'public-read'
};
s3.getSignedUrl('putObject', params, (err, url) => {
  if (err) return console.log(err);
  res.json({ url: url });
});

这部分似乎工作正常。如果我记录它并且它将它传递给前端,我可以看到URL。然后在前端,我正在尝试使用axios和签名的URL上传文件:

.then(res => {
    var options = { headers: { 'Content-Type': fileType } };
    return axios.put(res.data.url, fileFromFileInput, options);
  }).then(res => {
    console.log(res);
  }).catch(err => {
    console.log(err);
  });
}

有了这个,我得到403 Forbidden错误。如果我按照链接,有一些XML有更多信息:

<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
...etc

8 个答案:

答案 0 :(得分:5)

您的请求需要与签名完全匹配。一个明显的问题是,即使您将其包含在签名中,您实际上并未在请求中包含预设ACL。改为:

var options = { headers: { 'Content-Type': fileType, 'x-amz-acl': 'public-read' } };

答案 1 :(得分:3)

有同样的问题,这就是你需要解决的问题,

  1. 提取签名网址的文件名部分。 使用查询字符串参数执行正确提取文件名部分的打印件。这很关键。
  2. 编码为URI使用查询字符串参数对文件名进行编码。
  3. 使用编码文件名以及其他路径或节点服务从lambda返回url。
  4. 现在从带有该网址的axios发布,​​它会起作用。

    <强> EDIT1: 如果您传入错误的内容类型,您的签名也将无效。

    请确保您创建预先签名的网址的内容类型与您用于放置网址的内容类型相同。

    希望它有所帮助。

答案 2 :(得分:0)

1)您可能需要使用S3V4签名,具体取决于数据如何传输到AWS(块与流)。按如下方式创建客户端:

var s3 = new AWS.S3({
  signatureVersion: 'v4'
});

2)不要添加新标头或修改现有标头。请求必须完全符合签名。

3)确保生成的网址与发送给AWS的网址相匹配。

4)在签名之前发出测试请求删除这两行(并从PUT中删除标题)。这有助于缩小您的问题范围:

  ContentType: req.body.fileType,
  ACL: 'public-read'

答案 3 :(得分:0)

如果您尝试使用ACL,请确保您的Lambda IAM角色具有给定存储桶的s3:PutObjectAcl,并且您的存储桶还允许上传主体(用户使用s3:PutObjectAcl / iam /正在上传的帐户。

这是我仔细检查了所有标头和所有其他内容后为我解决的问题。

受到这个答案https://stackoverflow.com/a/53542531/2759427

的启发

答案 4 :(得分:0)

由于一些并不立即显而易见的原因,也可能发生预签名的s3放置上传的403禁止错误:

  1. 如果您使用通配符内容类型(例如image/*)生成预签名的放置网址,则可能会发生这种情况,因为不支持通配符。

  2. 如果您生成带有未指定内容类型的预先签名的put URL,然后在从浏览器上传时传入内容类型标头,则可能会发生这种情况。如果在生成url时未指定内容类型,则在上传时必须忽略该内容类型。请注意,如果您使用的是Uppy之类的上传工具,即使您未指定内容上传标头,它也可能会自动附加一个内容类型标头。在这种情况下,您必须手动将内容类型标头设置为空。

无论如何,如果您希望支持上传任何文件类型,则最好将文件的内容类型传递到api端点,并在生成返回给客户端的预签名URL时使用该内容类型。

例如,从您的api生成一个预签名的网址:

const AWS = require('aws-sdk')
const uuid = require('uuid/v4')

async function getSignedUrl(contentType) {
    const s3 = new AWS.S3({
        accessKeyId: process.env.AWS_KEY,
        secretAccessKey: process.env.AWS_SECRET_KEY
    })
    const signedUrl = await s3.getSignedUrlPromise('putObject', {
        Bucket: 'mybucket',
        Key: `uploads/${uuid()}`,
        ContentType: contentType
    })

    return signedUrl
}

然后从浏览器发送上传请求:

import Uppy from '@uppy/core'
import AwsS3 from '@uppy/aws-s3'

this.uppy = Uppy({
    restrictions: {
        allowedFileTypes: ['image/*'],
        maxFileSize: 5242880, // 5 Megabytes
        maxNumberOfFiles: 5
    }
}).use(AwsS3, {
    getUploadParameters(file) {
        async function _getUploadParameters() {
            let signedUrl = await getSignedUrl(file.type)
            return {
                method: 'PUT',
                url: signedUrl
            }
        }

        return _getUploadParameters()
    }
})

有关更多参考,请参阅以下两个堆栈溢出文章:how-to-generate-aws-s3-pre-signed-url-request-without-knowing-content-typeS3.getSignedUrl to accept multiple content-type

答案 5 :(得分:0)

此代码可以使用我几年前创建的凭据和存储桶,但在最近创建的凭据/存储桶上导致403错误:

const s3 = new AWS.S3({
  region: region,
  accessKeyId: process.env.AWS_ACCESS_KEY,
  secretAccessKey: process.env.AWS_SECRET_KEY,
})

解决方法只是添加signatureVersion: 'v4'

const s3 = new AWS.S3({
  signatureVersion: 'v4',
  region: region,
  accessKeyId: process.env.AWS_ACCESS_KEY,
  secretAccessKey: process.env.AWS_SECRET_KEY,
})

为什么?我不知道。

答案 6 :(得分:0)

正如其他人指出的,解决方案是添加签名版本。

const s3 = new AWS.S3(
  {
    apiVersion: '2006-03-01',
      signatureVersion: 'v4'
  }
);

有非常详细的讨论,看一下https://github.com/aws/aws-sdk-js/issues/468

答案 7 :(得分:0)

TLDR:检查您的存储桶是否存在并且可由生成签名 URL 的 AWS 密钥访问..

所有答案都非常好,很可能是真正的解决方案,但我的问题实际上源于 S3 将签名 URL 返回到不存在的存储桶。

因为服务器没有抛出任何错误,我认为一定是上传导致了问题,却没有意识到我的本地服务器在它的 .env 文件中有一个旧的存储桶名称,它曾经是正确的,但此后已被移动。

旁注:此链接帮助https://aws.amazon.com/premiumsupport/knowledge-center/s3-troubleshoot-403/

在检查上传用户 IAM 策略时,我发现该用户有权访问多个存储桶,但其中只有 1 个已存在。