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

部署在Vercel的客户端使用AWS S3预签名URL上传大文件报403错误

问题:Vercel部署后S3预签名URL上传大文件(>12MB)返回403 Forbidden
  • 本地环境上传任意大小文件均正常;部署到Vercel后,文件小于12MB可正常上传,超过12MB时会在几秒内返回403错误,排除超时因素。

生成预签名URL的接口代码

const AWS = require('aws-sdk');

AWS.config.update({
    accessKeyId: process.env.NEXT_PUBLIC_AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.NEXT_PUBLIC_AWS_SECRET_ACCESS_KEY,
    region: 'us-east-2',
});

const s3 = new AWS.S3(); // 原代码此处为小写aws,已修正为大写AWS

export default async function handler(req, res) {
    if (req.method === 'POST') {
        const { fileName, fileType, filePath, fileId } = req.body;

        if (!fileName || !fileType) {
            return res.status(400).json({ error: 'File name and type are required' });
        }

        const s3Params = {
            Bucket: 'test-bucket',
            Key: `${filePath}/${fileId}-${fileName}`,
            Expires: 1000,
            ContentType: fileType,
            ACL: 'private',
        };

        s3.getSignedUrl('putObject', s3Params, (err, data) => {
            if (err) {
                return res.status(500).json({ error: 'Failed to generate a pre-signed URL', details: err.message });
            }
            return res.status(200).json({ signedRequest: data });
        });
    } else {
        res.setHeader('Allow', ['POST']);
        res.status(405).end(`Method ${req.method} Not Allowed`);
    }
}

前端批量上传处理代码

const handleFilesChange = async (e) => {
    const files = Array.from(e.target.files);

    files.forEach(async (file) => {
        try {
            const fileId = uuidv4();
            const response = await axios.post('/api/s3-signature', {
                fileName: file.name,
                fileType: file.type,
                fileId: fileId,
                filePath: 'test-folder',
            });

            const signedRequest = response.data.signedRequest;

            await axios.put(signedRequest, file, {
                headers: {
                    'Content-Type': file.type,
                },
                timeout: 60000,
                onUploadProgress: (progressEvent) => {
                    const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
                    setFileProgress((prevState) => ({
                        ...prevState,
                        [file.name]: {
                            progress: percentCompleted,
                            type: file.type === 'application/pdf' ? 'pdf' : 'image',
                            size: file.size,
                        },
                    }));
                },
            });
        } catch (error) {
            console.log({ error });
        }
    });
};

问题排查与解决方案

1. 修复接口代码的大小写错误

原接口中const s3 = new aws.S3();为小写aws,与导入的大写AWS不匹配,本地环境可能因兼容未报错,但Vercel部署后会导致预签名URL生成异常,直接引发大文件上传验证失败。需修正为const s3 = new AWS.S3();。

2. 显式指定ContentLength参数

S3预签名URL默认会验证上传文件的Content-Length与请求头一致,大文件上传时若未在预签名参数中指定ContentLength,易因校验不通过返回403。修改如下:

  • 前端请求签名时新增fileSize字段:
    const response = await axios.post('/api/s3-signature', {
        // 原有字段
        fileSize: file.size // 新增文件大小参数
    });
    
  • 后端接口中添加ContentLength到S3参数:
    const { fileName, fileType, filePath, fileId, fileSize } = req.body;
    const s3Params = {
        // 原有参数
        ContentLength: fileSize // 新增
    };
    

3. 检查S3 Bucket的CORS配置

确保Bucket允许PUT请求且无文件大小限制,示例配置:

<CORSConfiguration>
    <CORSRule>
        <AllowedOrigin>你的Vercel域名</AllowedOrigin> <!-- 或指定*允许所有来源 -->
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
    </CORSRule>
</CORSConfiguration>

4. 验证前端请求Headers一致性

确保PUT请求的Content-Type与预签名URL中指定的ContentType完全匹配,避免因类型不匹配触发S3权限拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 07:27:21