尝试使用 Boto3 (Django + Javascript) 将文件上传到带有预签名 post 的 AWS S3 时被禁止访问 403

Getting 403 Forbidden when trying to upload file to AWS S3 with presigned post using Boto3 (Django + Javascript)

我已经尝试在 SO 和其他论坛上研究其他主题,但仍然无法解决这个问题。我正在为 S3 生成预​​签名 post 并尝试使用这些 headers 将文件上传到它,但收到 403:禁止访问。

权限 Boto3 加载的 IAM 用户具有对 S3 的列表、读取和写入权限。

CORS 允许来自所有来源和所有 headers 的 CORS

[
{
    "AllowedHeaders": [
        "*"
    ],
    "AllowedMethods": [
        "GET",
        "HEAD",
        "POST",
        "PUT"
    ],
    "AllowedOrigins": [
        "*"
    ],
    "ExposeHeaders": []
}
]

代码 该代码基于 Django 中的 Python 以及 Javascript。这是逻辑:

首先从 HTML 中检索文件,并用于调用一个函数来检索已签名的 URL。

(function () {
    document.getElementById("file-input").onchange = function () {
        let files = document.getElementById("file-input").files;
        let file = files[0];
        Object.defineProperty(file, "name", {
            writeable: true,
            value: `${uuidv4()}.pdf`

        })
        if (!file) {
            return alert("No file selected");
        }
        getSignedRequest(file);
    }
})();

然后使用 Django 视图(在下一节中描述)发送 GET 请求以检索已签名的 URL

function getSignedRequest(file) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "/sign_s3?file_name=" + file.name + "&file_type=" + file.type)
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                let response = JSON.parse(xhr.responseText)
                uploadFile(file, response.data, response.url)
            }
            else {
                alert("Could not get signed URL")
            }
        }
    };
    xhr.send()
}

生成签名的 Django 视图 URL

def Sign_s3(request):

    S3_BUCKET = os.environ.get("BUCKET_NAME")

    if (request.method == "GET"):
        file_name = request.GET.get('file_name')
        file_type = request.GET.get('file_type')
        
        s3 = boto3.client('s3', config = boto3.session.Config(signature_version = 's3v4'))
        
        presigned_post = s3.generate_presigned_post(
        Bucket = S3_BUCKET,
        Key = file_name,
        Fields = {"acl": "public-read", "Content-Type": file_type},
        Conditions = [
        {"acl": "public-read"},
        {"Content-Type": file_type}
        ],
        ExpiresIn = 3600
    )


    return JsonResponse({
        "data": presigned_post,
        "url": "https://%s.s3.amazonaws.com/%s" % (S3_BUCKET, file_name)
    })

最终文件应上传到存储桶(这是我收到 403 错误的地方)

function uploadFile(file, s3Data, url) {
            let xhr = new XMLHttpRequest();
            xhr.open("POST", s3Data.url)

            let postData = new FormData()
            for (key in s3Data.fields) {
                postData.append(key, s3Data.fields[key])
            }

            postData.append("file", file)

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200 || xhr.status === 204) {
                        document.getElementById("cv-url").value = url
                    }
                    else {
                        alert("Could not upload file")
                    }
                }
            };
            xhr.send(postData)
        }

网络请求 这是网络请求在浏览器中的样子

您应该在上传中使用的 URL 应该是预签名响应所具有的那个。不要只上传任何你想要的 url。

将您的回复更新为:

return JsonResponse({
  "data": presigned_post,
  "url": presigned_post.url
})

具体来说,您使用的 url 看起来像:

https://BUCKTET_NAME.s3.amazonaws.com/KEY_PATH

什么时候应该看起来像:

https://s3.REGION.amazonaws.com/BUCKET_NAME

然而,看看你的代码,这是它应该做的,但你从检查员那里截取的屏幕截图却另有说明。为什么网络请求中的 url 与 create_presigned_post 请求返回的 url 不匹配?

@jellycsc 帮助了我。我必须打开存储桶的 BlockPublicAcl 选项才能正常工作。