使用预签名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
相关产品推荐
相关产品推荐

