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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 02:20:34