AWS S3 createPresignedPost无法正常工作,求完整上传示例
预签名POST上传S3的正确实现及问题修复
核心问题分析
你的问题主要来自以下几点:
- IAM权限范围不足或配置错误
- 存储桶禁用ACL但代码中仍包含
acl字段 - 上传代码未正确处理响应及表单格式
- 预签名生成不实时验证凭证有效性(属于正常行为)
修正后的完整示例
1. 服务端生成预签名POST参数
import { S3Client } from "@aws-sdk/client-s3"; import { createPresignedPost } from "@aws-sdk/s3-presigned-post"; import { env } from "@/env/server.mjs"; // 初始化S3客户端 const s3Client = new S3Client({ region: "us-east-1", credentials: { accessKeyId: env.AWS_S3_ACCESS_KEY, secretAccessKey: env.AWS_S3_SECRET_KEY, }, }); // 配置上传参数 const bucketName = "my-unique-awsbucket"; const userId = 123; // 生成唯一键避免覆盖,可添加时间戳或文件哈希 const objectKey = `user/${userId}/avatar-${Date.now()}.jpg`; // 生成预签名POST const { url, fields } = await createPresignedPost(s3Client, { Bucket: bucketName, Key: objectKey, Fields: { key: objectKey, // 必须指定对象键 }, Expires: 600, // 有效期10分钟 // 可选:添加上传限制条件 Conditions: [ ["content-length-range", 0, 10 * 1024 * 1024], // 最大10MB ["starts-with", "$Content-Type", "image/"], // 仅允许图片类型 ], }); console.log("预签名信息:", { url, fields });
2. 客户端/服务端上传代码(form.submit)
import FormData from "form-data"; import fs from "fs"; import path from "path"; const form = new FormData(); // 追加所有预签名字段 Object.entries(fields).forEach(([key, value]) => { form.append(key, value); }); // 追加文件(使用流更高效) const imagePath = path.join(process.cwd(), "mountains.jpg"); form.append("file", fs.createReadStream(imagePath), { filename: "mountains.jpg", contentType: "image/jpeg", // 匹配Conditions中的类型限制 }); // 提交表单并处理响应 form.submit(url, (err, res) => { if (err) { console.error("上传失败:", err); return; } console.log("响应状态码:", res.statusCode); console.log("响应状态信息:", res.statusMessage); // 读取S3返回的错误详情 let responseBody = ""; res.on("data", (chunk) => responseBody += chunk); res.on("end", () => { if (res.statusCode >= 400) { console.error("S3错误响应:", responseBody); } else { console.log("文件上传成功!"); } }); });
3. Axios上传代码
import axios from "axios"; import FormData from "form-data"; import fs from "fs"; import path from "path"; const form = new FormData(); Object.entries(fields).forEach(([key, value]) => { form.append(key, value); }); const imagePath = path.join(process.cwd(), "mountains.jpg"); form.append("file", fs.createReadStream(imagePath), { filename: "mountains.jpg", contentType: "image/jpeg", }); // 关键:使用form.getHeaders()自动设置正确的Content-Type(含boundary) axios.post(url, form, { headers: form.getHeaders(), }) .then((res) => { console.log("上传成功,状态码:", res.status); }) .catch((err) => { console.error("上传失败:", err.response?.data || err.message); });
关键修复点说明
1. IAM权限配置
确保IAM用户拥有s3:PutObject权限,且资源范围覆盖目标路径:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::my-unique-awsbucket/user/*" } ] }
2. 移除ACL字段
当前存储桶禁用ACL(错误提示AccessControlListNotSupported),需删除代码中的acl: "public-read"字段。若需公开访问文件,可通过存储桶策略设置,而非ACL。
3. 表单上传注意事项
- 不要手动设置
Content-Length或Content-Type头部,让form-data库自动处理 - 使用文件流而非
readFileSync,提升大文件上传性能 - 必须检查响应状态码,
form.submit的回调仅捕获网络错误,不处理S3返回的业务错误(如403/400)
4. 凭证验证机制
预签名POST生成时不会实时验证凭证有效性,只有当实际发送上传请求到S3时,才会校验签名。因此错误凭证生成的预签名参数在上传时会被拒绝。
存储桶策略与CORS建议
- 存储桶策略:当前配置仅允许公开读取,无需修改(若不需要公开访问可移除)
- CORS策略:当前配置已满足上传需求,若需更严格控制可缩小
AllowedOrigins范围
内容的提问来源于stack exchange,提问作者wongx
相关产品推荐
相关产品推荐

