尝试使用 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
选项才能正常工作。
我已经尝试在 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
选项才能正常工作。