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

AWS无法连接S3存储桶:预签名URL无效及认证方式疑问

问题:AWS认证方式是否正确?构造的S3上传URL无效

后端REST端点代码

import aws from 'aws-sdk'

export default async function handler(req, res) {
    try {
        // 1.
        const s3 = new aws.S3({
            accessKeyId: process.env.APP_AWS_ACCESS_KEY,
            secretAccessKey: process.env.APP_AWS_SECRET_KEY,
            region: process.env.APP_AWS_REGION,
        })

        // 2.
        aws.config.update({
            accessKeyId: process.env.APP_AWS_ACCESS_KEY,
            secretAccessKey: process.env.APP_AWS_SECRET_KEY,
            region: process.env.APP_AWS_REGION,
            signatureVersion: 'v4',
        })

        console.log('bucket: ' + process.env.AWS_S3_BUCKET_NAME)
        // 3.
        const post = await s3.createPresignedPost({
            Bucket: process.env.AWS_S3_BUCKET_NAME,
            Fields: {
                key: req.query.file,
            },
            Expires: 60, // seconds
            Conditions: [
                ['content-length-range', 0, 5048576], // 最大1MB
            ],
        })

        console.log('post req: ' + JSON.stringify(post))
        // 4.
        return res.status(200).json(post)
    } catch (error) {
        console.log(error)
    }
}

构造的无效URL日志

bucket: 'arn:aws:s3:::my-app-bucket'  # Will be used in an API route.
post req: {"url":"https://s3.eu/'arn:aws:s3:::my-app-bucket'  %23 Will be used in an API route."

前端上传代码

const uploadPhoto = async (e) => {
    const file = e.target.files[0];
    const filename = encodeURIComponent(file.name);
    const res = await fetch(`/api/upload-image?file=${filename}`);
    const data = await res.json();
    const formData = new FormData();

    console.log("uploadPhoto()");
    console.log("file " + JSON.stringify(file));
    console.log("filename " + filename);
    console.log("data " + JSON.stringify(data));

    // @ts-ignore
    Object.entries({ ...data.fields, file }).forEach(([key, value]) => {
      formData.append(key as string, value as string);
    });

    toast.promise(
      fetch(data.url, {
        method: "POST",
        body: formData,
      }),
      {
        loading: "Uploading...",
        success: "Upload successful",
        error: `Upload failed ${error}`,
      }
    );
};

前端日志信息

uploadPhoto()
admin.tsx?6c2c:49 file {}
admin.tsx?6c2c:50 filename user.png
admin.tsx?6c2c:51 data {"url":"https://s3.eu/'arn:aws:s3:::my-app'  %23 Will be used in an API route.","fields":{"key":"user.png","bucket":"'arn:aws:s3:::my-app'  # Will be used in an API route.","X-Amz-Algorithm":"AWS4-HMAC-SHA256","X-Amz-Credential":"AKIAZMB5ZBK2LSTW23DT/20221025/EU (Frankfurt) eu-central-1/s3/aws4_request","X-Amz-Date":"20221025T133228Z",

问题分析与解决

1. 核心问题:Bucket配置错误

从日志可以明确看到,你的AWS_S3_BUCKET_NAME环境变量值包含了ARN格式、单引号和注释内容,这完全不符合S3 Bucket参数要求——Bucket参数只需要填写纯Bucket名称(比如my-app-bucket),不需要ARN格式,也不能带任何多余符号或注释。

2. 认证方式的优化

你的认证逻辑本身可行,但存在冗余:步骤1已经初始化了带配置的S3实例,步骤2又全局更新aws.config,这两步重复了。可以删掉其中一个,推荐保留全局配置并简化代码:

import aws from 'aws-sdk'

export default async function handler(req, res) {
    try {
        // 全局配置一次即可
        aws.config.update({
            accessKeyId: process.env.APP_AWS_ACCESS_KEY,
            secretAccessKey: process.env.APP_AWS_SECRET_KEY,
            region: process.env.APP_AWS_REGION,
            signatureVersion: 'v4',
        })

        const s3 = new aws.S3()

        // 确保Bucket名称无多余字符
        const bucketName = process.env.AWS_S3_BUCKET_NAME.trim()
        console.log('bucket: ' + bucketName)
        
        const post = await s3.createPresignedPost({
            Bucket: bucketName,
            Fields: {
                key: req.query.file,
            },
            Expires: 60,
            Conditions: [
                ['content-length-range', 0, 5048576],
            ],
        })

        console.log('post req: ' + JSON.stringify(post))
        return res.status(200).json(post)
    } catch (error) {
        console.log(error)
        res.status(500).json({ error: error.message }) // 补充错误响应返回
    }
}

3. 前端代码修复

你在formData.append时把File对象转成了字符串,会导致文件内容丢失,需单独添加文件对象:

// 修正后的FormData构建逻辑
Object.entries(data.fields).forEach(([key, value]) => {
  formData.append(key, value);
});
formData.append('file', file); // 直接传入File对象,不要转字符串

4. 环境变量修正

将AWS_S3_BUCKET_NAME的值改为纯Bucket名称,例如my-app-bucket,删除所有多余的ARN、引号和注释内容。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 09:41:29