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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46869962/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 07:00:02  来源:igfitidea点击:

Getting 403 (Forbidden) when uploading to S3 with a signed URL

javascriptnode.jsamazon-web-servicesamazon-s3axios

提问by Glenn

I'm trying to generate a pre-signed URL then upload a file to S3 through a browser. My server-side code looks like this, and it generates the URL:

我正在尝试生成一个预先签名的 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 });
});

This part seems to work fine. I can see the URL if I log it and it's passing it to the front-end. Then on the front end, I'm trying to upload the file with axios and the signed 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);
  });
}

With that, I get the 403 Forbidden error. If I follow the link, there's some XML with more info:

有了这个,我得到了 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

采纳答案by Michael - sqlbot

Your request needs to match the signature, exactly. One apparent problem is that you are not actually including the canned ACL in the request, even though you included it in the signature. Change to this:

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

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

回答by Kannaiyan

Had the same issue, here is how you need to solve it,

有同样的问题,这是您需要解决的方法,

  1. Extract the filename portion of the signed URL. Do a print that you are extracting your filename portion correctly with querystring parameters. This is critical.
  2. Encode to URI Encoding of the filename with query string parameters.
  3. Return the url from your lambda with encoded filename along with other path or from your node service.
  1. 提取签名 URL 的文件名部分。打印您正在使用查询字符串参数正确提取文件名部分。这很关键。
  2. Encode to URI 使用查询字符串参数对文件名进行编码。
  3. 从您的 lambda 返回带有编码文件名以及其他路径或您的节点服务的 url。

Now post from axios with that url, it will work.

现在使用该网址从 axios 发布,它将起作用。

EDIT1:Your signature will also be invalid, if you pass in wrong content type.

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

Please ensure that the content-type you have you create the pre-signed url is same as the one you are using it for put.

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

Hope it helps.

希望能帮助到你。

回答by Coburn

If you're trying to use an ACL, make sure that your Lambda IAM role has the s3:PutObjectAclfor the given Bucket and also that your bucket allows for the s3:PutObjectAclfor the uploading Principal (user/iam/account that's uploading).

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

This is what fixed it for me after double checking all my headers and everything else.

这是在仔细检查我的所有标题和其他所有内容后为我修复的。

Inspired by this answer https://stackoverflow.com/a/53542531/2759427

受此答案启发https://stackoverflow.com/a/53542531/2759427

回答by Roman Scher

Receiving a 403 Forbidden error for a pre-signed s3 put upload can also happen for a couple of reasons that are not immediately obvious:

收到 403 Forbidden 错误的预签名 s3 put 上传也可能由于以下几个原因而发生,这些原因并不明显:

  1. It can happen if you generate a pre-signed put url using a wildcardcontent type such as image/*, as wildcards are not supported.

  2. It can happen if you generate a pre-signed put url with no content type specified, but then pass in a content type header when uploading from the browser. If you don't specify a content type when generating the url, you have to omit the content type when uploading. Be conscious that if you are using an upload tool like Uppy, it may attach a content type header automatically even when you don't specify one. In that case, you'd have to manually set the content type header to be empty.

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

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

In any case, if you want to support uploading any file type, it's probably best to pass the file's content type to your api endpoint, and use that content type when generating your pre-signed url that you return to your client.

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

For example, generating a pre-signed url from your api:

例如,从您的 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
}

And then sending an upload request from the browser:

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

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()
    }
})

For further reference also see these two stack overflow posts: how-to-generate-aws-s3-pre-signed-url-request-without-knowing-content-typeand S3.getSignedUrl to accept multiple content-type

如需进一步参考,请参阅这两个堆栈溢出帖子:how-to-generate-aws-s3-pre-signed-url-request-without-knowing-content-typeS3.getSignedUrl to accept multiple content-type

回答by John Hanley

1) You might need to use S3V4 signatures depending on how the data is transferred to AWS (chunk versus stream). Create the client as follows:

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

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

2) Do not add new headers or modify existing headers. The request must be exactly as signed.

2) 不要添加新的标题或修改现有的标题。请求必须与签名完全一致。

3) Make sure that the url generated matches what is being sent to AWS.

3) 确保生成的 url 与发送到 AWS 的内容匹配。

4) Make a test request removing these two lines before signing (and remove the headers from your PUT). This will help narrow down your issue:

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

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