使用预签名 URL 上传到 S3 存储桶会出现 403 禁止错误

Posted

技术标签:

【中文标题】使用预签名 URL 上传到 S3 存储桶会出现 403 禁止错误【英文标题】:Upload to S3 Bucket with Presigned URL gives 403 forbidden error 【发布时间】:2020-05-15 06:49:57 【问题描述】:

我正在尝试使用 AWS S3 预签名 URL 为 Vue 应用程序实现图片上传功能。第一步是向 API 发送请求,该 API 将创建签名 URL 以上传文件。这部分工作正常:

服务器端:

'use strict';

const aws = require('aws-sdk');
const config = require('../config');
const util = require('./util');
const uuidv4 = require('uuid/v4');

const bucketName = 'myAmazonS3Bucket';

aws.config.update(
  secretAccessKey: config.AWS_SECRET_ACCESS_KEY,
  accessKeyId: config.AWS_ACCESS_KEY_ID,
  region: 'us-west-2'
);

const s3 = new aws.S3( signatureVersion: 'v4' );

const handler = async (event) => 
    console.log('Uploading file...');

    return await getUploadURL();


const getUploadURL = async () => 
    const actionId = uuidv4();

    const s3Params = 
      Bucket: bucketName,
      Key:  `$actionId.jpg`,
      ContentType: 'image/jpeg',
      ACL: 'public-read'
    ;

    console.log(s3Params);

    return new Promise((resolve, reject) => 
        let uploadURL = s3.getSignedUrl('putObject', s3Params);

        console.log(uploadURL);

        resolve(
            "statusCode": 200,
            "isBase64Encoded": false,
            "headers":  "Access-Control-Allow-Origin": "*" ,
            "body": JSON.stringify(
                "uploadURL": uploadURL,
                "photoFilename": `$actionId.jpg`
            )
        );

        reject(
            "statusCode": 500,
            "headers":  "Access-Control-Allow-Origin": "*" ,
            "body": "A funky error occurred and I am not happy about it!"
        )
    );


module.exports = 
    handler

API 端点发送一个类似下面的响应:


    "uploadURL": "https://s3.us-west-2.amazonaws.com/pics.amazon-clone.io/7925d452-cadd-4f06-ba63-cc50645e3cfb.jpg?AWSAccessKeyId=AKIASGDJJ5ZLUVPMUYMQ&Content-Type=image%2Fjpeg&Expires=1580276753&Signature=3rqNckP4DiL6DkWPRuEGJsuIGpw%3D&x-amz-acl=public-read",
    "photoFilename": "7925d452-cadd-4f06-ba63-cc50645e3cfb.jpg"

客户端将使用uploadUrl 将文件上传到S3 存储桶。这是客户端代码:

uploadImage: async function (e) 
            console.log('Upload clicked')
            console.log(e)

            // Get the presigned URL
            const response = await axios(
                method: 'POST',
                url: API_ENDPOINT
            )
            console.log('Response: ', response.data)
            console.log('Uploading: ', this.image)

            let binary = atob(this.image.split(',')[1])
            let array = []
            for (var i = 0; i < binary.length; i++) 
                array.push(binary.charCodeAt(i))
            
            let blobData = new Blob([new Uint8Array(array)], type: 'image/jpeg')
            console.log('Uploading to: ', response.data.uploadURL)
            const result = await fetch(response.data.uploadURL, 
                method: 'PUT',
                headers:  
                    'Content-Type': 'image/jpeg', 
                    'x-amz-acl': 'public-read' ,
                body: blobData
            )
            console.log('Result: ', result)
            // Final URL for the user doesn't need the query string params
            this.uploadURL = response.data.uploadURL.split('?')[0]
        

很遗憾,我在使用签名 URL 时遇到了禁止的 403 错误。这是我从 Chrome 浏览器得到的控制台错误的结果:

上传到: https://s3.us-west-2.amazonaws.com/pics.amazon-clone.io/b1bdb5e3-7f64-49f7-b779-11b3f67317ee.jpg?Content-Type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASGDJJ5ZLUVPMUYMQ%2F20200129%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200129T165522Z&X-Amz-Expires=900&X-Amz-Signature=b230c9a40065585307e150655466bbab3d0d99aa43f8620377ab977eb1c7234c&X-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read

Pic.vue?937b:60 选项 https://s3.us-west-2.amazonaws.com/pics.amazon-clone.io/b1bdb5e3-7f64-49f7-b779-11b3f67317ee.jpg?Content-Type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASGDJJ5ZLUVPMUYMQ%2F20200129%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200129T165522Z&X-Amz-Expires=900&X-Amz-Signature=b230c9a40065585307e150655466bbab3d0d99aa43f8620377ab977eb1c7234c&X-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read 403(禁止)

访问获取 'https://s3.us-west-2.amazonaws.com/pics.amazon-clone.io/b1bdb5e3-7f64-49f7-b779-11b3f67317ee.jpg?Content-Type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASGDJJ5ZLUVPMUYMQ%2F20200129%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200129T165522Z&X-Amz-Expires=900&X-Amz-Signature=b230c9a40065585307e150655466bbab3d0d99aa43f8620377ab977eb1c7234c&X-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read' 来自原点“http://localhost:8080”已被 CORS 策略阻止: 对预检请求的响应未通过访问控制检查:否 请求中存在“Access-Control-Allow-Origin”标头 资源。如果不透明的响应满足您的需求,请设置请求的 模式为“no-cors”以获取禁用 CORS 的资源。

【问题讨论】:

如果这是一个 Lambda 函数,您应该强烈考虑利用 IAM 角色,而不是在使用 Lambda 函数打包和上传的配置文件中提供硬编码的 AWS 凭证。 大概您的目标 S3 存储桶允许使用公开读取的预设 ACL 上传对象。并且您的客户端在预签名 URL 过期之前以及您用于创建预签名 URL 的凭据过期之前使用它。 【参考方案1】:

当您从 AWS S3 存储桶收到 OPTIONS -> CORS 策略错误时,这意味着您的存储桶配置不允许您上传图片。

要解决此问题,您必须更新存储桶的权限配置。

为此,请转到您的存储桶 -> 权限选项卡 -> CORS 选项卡

然后将以下代码放入其中:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin><YOUR_DOMAIN></AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

别忘了用你的域名替换&lt;YOUR_DOMAIN&gt;,很明显:P

If you want to allow all domains just replace this line

<AllowedOrigin><YOUR_DOMAIN></AllowedOrigin> 

with this

<AllowedOrigin>*</AllowedOrigin>

【讨论】:

哦,我忘了说我已经在我的存储桶中设置了 CORS 配置策略。这是一件很明显的事情,我没想到要提它。 为了确定,首先检查您的文件类型是否错误。如我所见,您使用 .jpg 命名它,但内容类型为 jpeg 如果这没有帮助,请尝试执行此请求以上传:axios.put(presignedUrl, null, file, isMultipartForm: false, headers: 'Content -Type': 文件类型 );希望它有所帮助;) 另一件可能有帮助的事情是由客户端生成 presignedUrl 和 uploadFile 您建议生成 presignedUrl 客户端。如何在不泄露 AWS 访问密钥和密钥的情况下做到这一点? 无需在客户端创建预签名 URL。这是一条红鲱鱼,通常是一种糟糕的安全做法。【参考方案2】:

这是我将如何进行故障排除。我会首先使用 curl 或 postman 等工具确认生成的 URL 是否正常工作,然后我会检查发送到 s3 的请求并查找请求中存在的任何其他标头。

第 1 步 - 确认生成的 URL 正常

我会尝试通过 curl/postman 上传以查看 URL 是否有效。

curl -X PUT -T ~/Downloads/car.jpg https://s3.us-west-2.amazonaws.com/pics.amazon-clone.io/b1bdb5e3-7f64-49f7-b779-11b3f67317ee.jpg?Content-Type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASGDJJ5ZLUVPMUYMQ%2F20200129%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200129T165522Z&X-Amz-Expires=900&X-Amz-Signature=b230c9a40065585307e150655466bbab3d0d99aa43f8620377ab977eb1c7234c&X-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read

第 2 步 - 确认 reactjs fetch 请求是否正常工作

如果您可以确认第 1 步正在运行,那么您可以使用浏览器的网络选项卡检查与请求一起发送的实际标头。查看代码是否在您不知情的情况下发送任何其他标头。

希望这会有所帮助。

【讨论】:

【参考方案3】:

我也遇到了 403 错误。

就我而言,我只是设置了signatureVersion,并解决了它。

之前

const s3 = new aws.S3();

之后

const s3 = new aws.S3( signatureVersion: 'v4' );

【讨论】:

【参考方案4】:

如果您遇到 CORS 问题,请尝试从 curl 而不是前端应用程序上传图片,因为该问题可能与标题有关。例如:curl -v --upload-file "image.png" "https://s3.your-presigned-url"。如果您的前端发送“Content-type”标头,则需要在生成链接时包含“Content-Type”。 This is the reference I found for my solution

这是我的工作修改后的python代码,它解决了我的问题:

response = s3_client.generate_presigned_url('put_object',
                                                Params='Bucket': bucket_name,
                                                        'Key': object_name,
                                                        'ContentType':"image/png", #DONT FORGET THIS
                                                ExpiresIn=expiration)

【讨论】:

【参考方案5】:

错误显然是由于 CORS。希望你不介意,但我会建议一个更好的模式来做同样的用例。如果您愿意使用 API Gateway,那么它比使用预签名 url 更容易设置并且更安全。您还可以将前端代码与上传/下载逻辑解耦。

阅读:https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-upload-image-s3/

【讨论】:

以上是关于使用预签名 URL 上传到 S3 存储桶会出现 403 禁止错误的主要内容,如果未能解决你的问题,请参考以下文章

使用 AlamoFire 和预签名 URL 将图像上传到 S3 存储桶时出现问题

使用预签名 URL 将文件上传到 Amazon S3 时出现 CORS 错误

S3 预签名上传 url 错误

带有预签名 URL 和 CORS 问题的 S3 上传

使用预签名的 url PUT 到 S3 会出现 403 错误

如何使用预签名的 url 将对象放入 amazon s3?