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

Getting 403 (Forbidden) when uploading to S3 with a signed 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 });
});

这部分似乎工作正常。如果我记录它并将它传递到前端,我可以看到 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 错误。如果我关注 link,会有一些 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

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

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

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

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

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

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

遇到了同样的问题,这是您需要解决的方法,

  1. 提取已签名 URL 的文件名部分。 打印您正在使用查询字符串参数正确提取文件名部分。这很关键。
  2. 使用查询字符串参数编码为文件名的 URI 编码。
  3. Return 来自您的 lambda 的 url 以及其他路径或来自您的节点服务的编码文件名。

现在 post 来自 axios url,它将起作用。

编辑 1: 如果您输入错误的内容类型,您的签名也将无效。

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

希望对您有所帮助。

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

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

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

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

受到这个答案的启发https://whosebug.com/a/53542531/2759427

收到 pre-signed s3 put 上传的 403 禁止错误也可能由于一些不明显的原因而发生:

  1. 如果您使用 通配符 内容类型(例如 image/* 生成 pre-signed put url,就会发生这种情况,因为不支持通配符。

  2. 如果您生成 pre-signed put url 并且 未指定内容类型,然后传入内容,则可能会发生这种情况从浏览器上传时输入 header。如果在生成 url 时未指定内容类型,则上传时必须省略内容类型。请注意,如果您使用 Uppy 等上传工具,即使您未指定内容类型,它也可能会自动附加内容类型 header。在这种情况下,您必须手动将内容类型 header 设置为空。

无论如何,如果您想支持上传任何文件类型,最好将文件的内容类型传递给您的 api 端点,并在生成您的 pre-signed 时使用该内容类型url 你 return 给你的客户。

例如,从您的 api:

生成 pre-signed 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-type and S3.getSignedUrl to accept multiple content-type

这段代码使用了我几年前创建的凭据和存储桶,但在最近创建的 credentials/buckets:

上导致了 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,
})

为什么?我不知道。

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

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

同样有很详细的讨论看看https://github.com/aws/aws-sdk-js/issues/468

TLDR:检查您的存储桶是否存在并且可以通过生成签名 URL..

的 AWS 密钥访问

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

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

旁注:这 link 帮助了 https://aws.amazon.com/premiumsupport/knowledge-center/s3-troubleshoot-403/

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

在上传文件时使用 python boto3,默认情况下权限是私有的。您可以使用 ACL='public-read'

使对象 public
s3.put_object_acl(
     Bucket='gid-requests', Key='potholes.csv', ACL='public-read')

我做了这里提到的所有事情并允许这些权限让它工作: