使用签名 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'
遇到了同样的问题,这是您需要解决的方法,
- 提取已签名 URL 的文件名部分。
打印您正在使用查询字符串参数正确提取文件名部分。这很关键。
- 使用查询字符串参数编码为文件名的 URI 编码。
- 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 和其他所有内容后为我修复的。
收到 pre-signed s3 put 上传的 403 禁止错误也可能由于一些不明显的原因而发生:
如果您使用 通配符 内容类型(例如 image/*
生成 pre-signed put url,就会发生这种情况,因为不支持通配符。
如果您生成 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')
我做了这里提到的所有事情并允许这些权限让它工作:
我正在尝试生成预签名 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'
遇到了同样的问题,这是您需要解决的方法,
- 提取已签名 URL 的文件名部分。 打印您正在使用查询字符串参数正确提取文件名部分。这很关键。
- 使用查询字符串参数编码为文件名的 URI 编码。
- 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 和其他所有内容后为我修复的。
收到 pre-signed s3 put 上传的 403 禁止错误也可能由于一些不明显的原因而发生:
如果您使用 通配符 内容类型(例如
image/*
生成 pre-signed put url,就会发生这种情况,因为不支持通配符。如果您生成 pre-signed put url 并且 未指定内容类型,然后传入内容,则可能会发生这种情况从浏览器上传时输入 header。如果在生成 url 时未指定内容类型,则上传时必须省略内容类型。请注意,如果您使用 Uppy 等上传工具,即使您未指定内容类型,它也可能会自动附加内容类型 header。在这种情况下,您必须手动将内容类型 header 设置为空。
无论如何,如果您想支持上传任何文件类型,最好将文件的内容类型传递给您的 api 端点,并在生成您的 pre-signed 时使用该内容类型url 你 return 给你的客户。
例如,从您的 api:
生成 pre-signed urlconst 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'
使对象 publics3.put_object_acl(
Bucket='gid-requests', Key='potholes.csv', ACL='public-read')
我做了这里提到的所有事情并允许这些权限让它工作: