You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用预签名URL从JS前端上传文件到S3 Bucket返回400错误

问题描述

已配置好S3 Bucket及具备读写权限的用户,后端通过Python代码生成预签名POST URL,后端使用requests上传文件正常,但前端用JavaScript上传时收到400错误。

后端Python生成预签名POST URL代码

def create_presigned_post(bucket_name, object_name,
                          fields=None, conditions=None, expiration=3600):
    """Generate a presigned URL S3 POST request to upload a file

    :param bucket_name: string
    :param object_name: string
    :param fields: Dictionary of prefilled form fields
    :param conditions: List of conditions to include in the policy
    :param expiration: Time in seconds for the presigned URL to remain valid
    :return: Dictionary with the following keys:
        url: URL to post to
        fields: Dictionary of form fields and values to submit with the POST
    :return: None if error.
    """

    # Generate a presigned S3 POST URL
    s3_client = boto3.client('s3',
                            aws_access_key_id=settings.AWS['ACCESS_KEY'],
                            aws_secret_access_key=settings.AWS['SECRET_KEY'])

    try:
        response = s3_client.generate_presigned_post(bucket_name,
                                                    object_name,
                                                    Fields=fields,
                                                    Conditions=conditions,
                                                    ExpiresIn=expiration)
        print(response['fields'])
        return response
    except ClientError as e:
        raise e

后端Python上传测试代码(正常工作)

response = create_presigned_post('BUCKET_NAME', object_name)
if response is None:
    exit(1)

# Demonstrate how another Python program can use the presigned URL to upload a file
with open(object_name, 'rb') as f:
    files = {'file': (object_name, f)}
    http_response = requests.post(response['url'], data=response['fields'], files=files)
# If successful, returns HTTP status code 204
logging.info(f'File upload HTTP status code: {http_response.status_code}')

前端JavaScript上传代码(报400错误)

if (res.ok) {
    const resData = await res.json();
    let formData = new FormData();
    let formFields = JSON.parse(resData['fields']);
    formData.append('file', tarFile);
    formData.append('key', formFields['key']);
    formData.append('x-amz-algorithm', formFields['x-amz-algorithm']);
    formData.append('x-amz-credential', formFields['x-amz-credential']);
    formData.append('x-amz-date', formFields['x-amz-date']);
    formData.append('policy', formFields['policy']);
    formData.append('x-amz-signature', formFields['x-amz-signature']);

    const response = await fetch(resData['url'], {
        method: 'POST',
        body: data,
    });
    return response;
}

问题分析与修复

1. 变量名错误

前端代码中创建的表单数据变量是formData,但fetch请求的body参数用的是data,导致请求未携带任何表单数据,直接触发400错误。

2. 不必要的JSON解析

后端返回的fields本身就是JSON对象,无需调用JSON.parse解析。后端返回的response字典中fields是原生字典,前端接收后可直接作为对象使用。

3. 手动添加字段易遗漏

手动逐个添加签名字段容易出现拼写错误或遗漏,应遍历resData.fields的所有键值对批量添加到formData。


修复后的前端代码
if (res.ok) {
    const resData = await res.json();
    const formData = new FormData();
    // 批量添加所有签名字段
    Object.entries(resData.fields).forEach(([key, value]) => {
        formData.append(key, value);
    });
    // 添加要上传的文件
    formData.append('file', tarFile);

    const response = await fetch(resData.url, {
        method: 'POST',
        body: formData,
        // 不要手动设置Content-Type,浏览器会自动生成正确的multipart/form-data边界
    });
    return response;
}

额外注意事项

  • 禁止手动设置Content-Type头:使用FormData作为fetch的body时,浏览器会自动生成包含边界信息的正确multipart/form-data头,手动设置会导致S3验证失败。
  • 确保tarFile是有效的File对象:检查前端获取的文件是否正确,避免传递空或无效的文件对象。

内容的提问来源于stack exchange,提问作者StrandedYax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 18:45:31