使用签名 URL 上传到 S3 时出现 403(禁止)
Posted
技术标签:
【中文标题】使用签名 URL 上传到 S3 时出现 403(禁止)【英文标题】:Getting 403 (Forbidden) when uploading to S3 with a signed URL 【发布时间】:2018-04-02 20:33:34 【问题描述】:我正在尝试生成一个预签名的 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
【问题讨论】:
【参考方案1】:1) 您可能需要使用 S3V4 签名,具体取决于数据传输到 AWS 的方式(块与流)。如下创建客户端:
var s3 = new AWS.S3(
signatureVersion: 'v4'
);
2) 不要添加新标题或修改现有标题。请求必须与签名完全一致。
3) 确保生成的 url 与发送到 AWS 的内容相匹配。
4) 发出测试请求,在签名前删除这两行(并从 PUT 中删除标头)。这将有助于缩小您的问题范围:
ContentType: req.body.fileType,
ACL: 'public-read'
【讨论】:
谢谢,我浏览了这个列表,我相信可能存在标题不匹配的问题。如何比较已签名 url 中的标头与我发送的标头? 您在调用 getSignedUrl 时在参数中指定 HTTP 标头。另请注意 Michael sqlbot 使用 x-amz-acl 标头的回复。迈克尔的参数是正确的。确保在 PUT 中包含相同的标题。 一个附加项目。在签名和调用 put 时添加 Content-Length 和文件大小。我没有看到指定的区域。创建客户端时添加此项。【参考方案2】:遇到了同样的问题,下面是你需要解决的方法,
-
提取签名 URL 的文件名部分。
打印您正在使用查询字符串参数正确提取文件名部分。这很关键。
使用查询字符串参数将文件名编码为 URI 编码。
从带有编码文件名的 lambda 以及其他路径或节点服务返回 url。
现在使用该网址从 axios 发布,它将起作用。
EDIT1: 如果您传递了错误的内容类型,您的签名也将无效。
请确保您创建预签名 url 的内容类型与您用于 put 的内容类型相同。
希望对你有帮助。
【讨论】:
谢谢,我试了一下,但我不认为这是问题所在。该 URL 似乎已经正确编码,如果我尝试“重新编码”它,它实际上会变成一个死链接。 您能否确认您的内容类型与 axios 相同,然后您已经创建了您的签名网址?【参考方案3】:您的请求需要与签名完全匹配。一个明显的问题是您实际上并未在请求中包含预设 ACL,即使您将其包含在签名中也是如此。改成这样:
var options = headers: 'Content-Type': fileType, 'x-amz-acl': 'public-read' ;
【讨论】:
谢谢,不是这样,但我想我对问题所在有了更好的了解。我很确定有些东西不匹配,我只是不知道到底是什么。 不过,这应该是其中的一部分。这是一个非常确定的过程,对不一致的容忍度为零。错误消息中的CanonicalRequest
会间接告诉您,根据您实际发送的请求,您应该将哪些参数传递给getSignedUrl()
。
谢谢,终于确定了正确的headers并成功提出了put请求。
你放了什么标题。我也面临同样的问题。
@Alan 你能分享一下你是如何解决这个问题的吗?这样其他有相同问题的人可以找到解决方案,谢谢。【参考方案4】:
如果您尝试使用 ACL,请确保您的 Lambda IAM 角色具有给定存储桶的 s3:PutObjectAcl
,并且您的存储桶允许上传委托人的 s3:PutObjectAcl
(用户/iam/帐户正在上传)。
这是在仔细检查了我的所有标题和其他所有内容后为我解决的问题。
受此答案的启发https://***.com/a/53542531/2759427
【讨论】:
【参考方案5】:对于预签名的 s3 put 上传接收到 403 Forbidden 错误也可能由于几个并非立即显而易见的原因而发生:
如果您使用 通配符 内容类型(例如 image/*
)生成预签名的 put url,则可能会发生这种情况,因为不支持通配符。
如果您生成预签名的 put 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-type 和 S3.getSignedUrl to accept multiple content-type
【讨论】:
谢谢你。 PAW 和 JS fetch 都将默认内容类型添加到我的标题中 - 我没想到要检查!【参考方案6】:此代码使用我几年前创建的凭据和存储桶,但在最近创建的凭据/存储桶上导致 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,
)
为什么?我不知道。
【讨论】:
【参考方案7】:正如其他人指出的那样,解决方案是添加 signatureVersion。
const s3 = new AWS.S3(
apiVersion: '2006-03-01',
signatureVersion: 'v4'
);
周围有很详细的讨论看看https://github.com/aws/aws-sdk-js/issues/468
【讨论】:
【参考方案8】:TLDR:检查您的存储桶是否存在并且可由生成签名 URL 的 AWS 密钥访问。
所有答案都非常好,很可能是真正的解决方案,但我的问题实际上源于 S3 将签名 URL 返回到不存在的存储桶。
因为服务器没有抛出任何错误,所以我假设它一定是导致问题的上传,而没有意识到我的本地服务器的 .env 文件中有一个旧的存储桶名称曾经是正确的,但后来被搬走了。
旁注:此链接帮助https://aws.amazon.com/premiumsupport/knowledge-center/s3-troubleshoot-403/
在检查上传用户的 IAM 政策时,我发现该用户可以访问多个存储桶,但其中只存在一个。
【讨论】:
【参考方案9】:在上传文件时使用 python boto3,默认情况下权限是私有的。您可以使用 ACL='public-read' 将对象公开
s3.put_object_acl(
Bucket='gid-requests', Key='potholes.csv', ACL='public-read')
【讨论】:
【参考方案10】:我做了这里提到的所有事情,并允许这些权限让它工作:
【讨论】:
以上是关于使用签名 URL 上传到 S3 时出现 403(禁止)的主要内容,如果未能解决你的问题,请参考以下文章
使用预签名 URL 将文件上传到 Amazon S3 时出现 CORS 错误
使用预签名 URL 上传到 S3 存储桶会出现 403 禁止错误
仅从浏览器使用预签名 URL 上传时出现 AWS S3 CORS 错误