S3预签名URL执行PUT请求返回403禁止错误求助
S3预签名URL PUT请求返回403 Forbidden的解决方法
问题场景
使用Next.js API生成S3预签名URL,前端通过PUT请求上传文件时返回403错误,已确认Content-Type无通配符且前后一致、请求头匹配,使用@aws-sdk/client-s3的S3Client无法设置signatureVersion: 'v4'。
后端API代码
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { type NextApiRequest, type NextApiResponse } from "next"; const s3 = new S3Client({ region: process.env.AWS_REGION + "", credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, }, }); export default async function signUrl( req: NextApiRequest, res: NextApiResponse ) { const { fileName, fileType } = req.body; const s3Params = { Bucket: process.env.AWS_BUCKET_NAME, Key: fileName, Expires: 3600, ContentType: fileType, }; try { const command = new GetObjectCommand(s3Params); const url = await getSignedUrl(s3, command); res.status(200).json({ url }); } catch (error) { console.log(error); res.status(500).end(); } }
前端调用代码
function handleLoadImage() { const input = document.createElement("input"); input.type = "file"; input.accept = "image/*"; input.onchange = async () => { if (!input.files || !input.files[0]) return; const file = input.files[0]; const response = await fetch("/api/signUrl", { method: "POST", body: JSON.stringify({ fileName: file.name, fileType: file.type }), headers: { "Content-Type": "application/json", }, }); if (!response.ok) throw new Error("Error generating signed URL"); const { url } = await response.json(); await fetch(url, { method: "PUT", body: file, headers: { "Content-Type": file.type, }, }); }; input.click(); }
核心问题
后端API使用了GetObjectCommand生成预签名URL,该命令对应GET请求,但前端实际发送的是PUT请求,签名与请求方法不匹配,导致S3返回403 Forbidden。
解决步骤
1. 替换后端命令类型
将GetObjectCommand替换为PutObjectCommand,确保生成的预签名URL对应PUT请求:
修改后的后端API代码:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; // 替换为PutObjectCommand import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { type NextApiRequest, type NextApiResponse } from "next"; const s3 = new S3Client({ region: process.env.AWS_REGION + "", credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, }, }); export default async function signUrl( req: NextApiRequest, res: NextApiResponse ) { const { fileName, fileType } = req.body; const s3Params = { Bucket: process.env.AWS_BUCKET_NAME, Key: fileName, Expires: 3600, ContentType: fileType, }; try { const command = new PutObjectCommand(s3Params); // 使用PutObjectCommand const url = await getSignedUrl(s3, command); res.status(200).json({ url }); } catch (error) { console.log(error); res.status(500).end(); } }
2. 验证IAM权限
确保生成预签名URL使用的IAM用户具备s3:PutObject权限,示例IAM策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::your-bucket-name/*" } ] }
同时检查Bucket策略是否允许该用户的Put操作,避免权限冲突。
3. 额外检查项
- 确认
fileName无特殊字符(如空格、中文等),若有需提前编码或处理,确保S3对象Key与预签名URL中的Key完全一致 - 检查S3 Bucket是否开启了CORS,允许前端域名的PUT请求(若前端与API不在同一域名下):
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>https://your-frontend-domain.com</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
内容的提问来源于stack exchange,提问作者GermanJablo
相关产品推荐
相关产品推荐

