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

新手上路,请多包涵

我试图生成一个预签名的 URL,然后通过浏览器将文件上传到 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

原文由 Glenn 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

您的请求需要与签名完全匹配。一个明显的问题是您实际上并没有在请求中包含标准 ACL,即使您在签名中包含了它。更改为:

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

原文由 Michael - sqlbot 发布,翻译遵循 CC BY-SA 3.0 许可协议

预签名 s3 put 上传收到 403 Forbidden 错误也可能由于一些不明显的原因而发生:

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

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

在任何情况下,如果你想支持上传任何文件类型,最好将文件的内容类型传递给你的 api 端点,并在生成你返回给客户端的预签名 url 时使用该内容类型。

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

 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

原文由 Roman Scher 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏