通过浏览器(putObject)在AWS S3上放置对象时出现意外错误



对于S3上的putObject请求,我通过Node/Express后端成功获得了一个预签名的URL,并尝试通过浏览器上传相关文件。

当我通过浏览器(或Postman(发出put请求时,我会得到400403。我是S3的新手,不确定在哪里可以查找到有关这方面的信息。

我知道如何正确获得预签名的URL,但为什么我对获得URL的文件的put请求失败了?

据我所知,我收到的请求是公开的。

我在这里查阅了不同的代码漫游教程、S3文档和帖子。

我的代码在前端请求预签名的URL

// Returns null or array of objects of type { url : string | null; contentType: string; }
const filesDescription = fileList[+key];
if (filesDescription === undefined || filesDescription === null) continue;
const Key          = `${prefix}/${filesDescription.name}`;
const ContentType  =  filesDescription.type;
const request  =  generateEndpointConfig(APIEndpoints.GET_PRESIGNED_URL, { Key, ContentType, userID });
const res      =  await apiCall(request.METHOD, request.URL, request.DATA);
const url = typeof res.data.signedUrl === "string" ? res.data.signedUrl : null;
presignedUrls.push({url, contentType:  ContentType});

我的Node/Express后端上获取URL 的代码

const { ContentType, Key } = req.body;
const s3 = new S3({
accessKeyId: AWS_ACCESS_ID,
secretAccessKey: AWS_SECRET
});
const url = await s3.getSignedUrlPromise("putObject",
{
Bucket: AMAZON_AWS_STATIC_BUCKET,
ContentType,
Key,
Expires: 300
});
return res.status(StatusCodes.OK).json({ signedUrl: url })

最后,put请求我上传文件

const presignedUrls = await getPresignedUrls(+idx);
if (presignedUrls === null) return false;
for (const fileIdx in presignedUrls)
{
const fileList      =  files[idx];
const uploadConfig  =  presignedUrls[fileIdx];
if (fileList === null || uploadConfig.url === null) continue;
const fileToUpload = fileList[fileIdx];
try
{
// Make put request for corresponding file to cloud service
await axios.put(uploadConfig.url, fileToUpload,
{
headers:
{
"Content-Type": uploadConfig.contentType
}
});
}
catch(err)
{
return false;
}

我还应该提到,因为我这样做是为了经过身份验证的用户,所以我还传输了形式Bearer <TOKEN>Authorization报头。

===编辑===

这是我在Postman中返回的状态代码为403的错误响应示例。

<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>...</RequestId>
<HostId>...</HostId>
</Error>

===编辑2===

我的水桶上的政策是

{
"Version": "2012-10-17",
"Id": "<POLICY_ID>",
"Statement": [
{
"Sid": "<STMT_ID>",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <CLOUDFRONT_ORIGIN_ID>"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<S3_BUCKET_NAME>/*"
},
{
"Sid": "<STMT_ID>",
"Effect": "Allow",
"Principal": {
"Federated": [
"http://localhost:3000",
"https://www.my-website.com/"
],
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<S3_BUCKET_NAME>/*"
}
]
}

我的bucket上的CORS配置是

[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"PUT",
"POST",
"DELETE"
],
"AllowedOrigins": [
"https://www.my-website.com",
"http://localhost:3000"
],
"ExposeHeaders": []
}
]

作为参考,此bucket为CDN网络提供CloudFront分发,并且该bucket仅在GET请求进入域级别时受到限制。

===编辑3===

这是我尝试在浏览器上上传时返回的错误。

<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<AWSAccessKeyId>...</AWSAccessKeyId>
<StringToSign>GET [A_LONG_NUMBER] /[BUCKET_NAME]/[PREFIX]/[IMAGE_NAME].jpg</StringToSign>
<SignatureProvided>...</SignatureProvided>
<StringToSignBytes>[SOME_BYTES]</StringToSignBytes>
<RequestId>...</RequestId>
<HostId>...</HostId>
</Error>

===最终编辑===

除了@jarmod的回答外,问题是我的应用程序配置在网站上的用户经过身份验证后,会自动将Authorization标头转发给所有请求。删除S3调用的此标头使该进程在浏览器上成功运行。

最初签署putObject URL时,您提供了IAM用户的AWS凭据(访问密钥和密钥(。稍后,当S3预签名的URL呈现给S3时,该URL的原始签名者,即IAM用户,将是上载的主体。因此,要么IAM用户主体需要IAM策略,允许在相关资源上使用s3:PutObject,要么您的bucket策略需要允许IAM用户主体使用此策略。

您的S3存储桶上有CORS配置设置吗?这指定了哪些源可以访问您的bucket,以及它们可以使用哪些HTTP方法。在这种情况下,您通常会在开发人员控制台中看到类似Access to fetch at 'YOUR-PRESIGNED-URL' from origin 'YOUR-ORIGIN' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource的错误

本页介绍了基本配置:https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html

最新更新