部署在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
相关产品推荐
相关产品推荐

