如何为S3预签名URL(PUT而非POST)添加元数据-JavaScript SDK V3
S3预签名PUT URL添加元数据触发403禁止错误的解决方案
问题核心
预签名URL生成后查询字符串包含元数据,但PUT请求返回403,核心原因是元数据的签名匹配逻辑不严谨,涉及键名大小写、参数重复定义、编码一致性三个问题。
具体问题与修复方案
1. 键名大小写不匹配
服务端签名头部用的是x-amz-meta-fileSize(首字母大写),但客户端请求头是x-amz-meta-filesize(全小写)。S3对自定义元数据的签名是严格区分大小写的,必须保证两端完全一致。
2. Metadata参数与签名头部重复定义
在PutObjectCommand的params.Metadata中定义元数据时,S3会自动生成对应的x-amz-meta-*请求头并加入签名逻辑。如果同时在signedHeaders中指定这些头部,会导致签名冲突——要么通过Metadata传递(无需额外签名头部),要么通过请求头传递(不在Metadata中定义)。
3. 编码重复处理
服务端和客户端都对origfilename执行了encodeURIComponent,导致双重编码,签名时内容不匹配。
修正后的代码示例
方案一:通过请求头传递元数据并签名(推荐灵活场景)
Lambda生成URL代码
const params={ Bucket: bucket, Key: key, // 移除Metadata,改为客户端通过请求头传递 }; console.log("params=", params); // 统一使用全小写键名,和客户端请求头保持一致 const headersToSign= new Set([ "x-amz-meta-filesize", "x-amz-meta-filetype", "x-amz-meta-origfilename", ]); const command=new PutObjectCommand(params); const url = await getSignedUrl(s3Client, command, { expiresIn: expiry, signedHeaders: headersToSign, });
客户端fetch代码
const encodedFileName = encodeURIComponent(fileToSubmit.name); const myHeaders = new Headers({ "x-amz-meta-filesize": fileToSubmit.size.toString(), // 确保为字符串类型 "x-amz-meta-filetype": fileToSubmit.type, "x-amz-meta-origfilename": encodedFileName, }); const myRequest = new Request(url, { method: 'PUT', mode: 'cors', cache: 'default', body: fileToSubmit, headers: myHeaders }); fetch(myRequest) .then(response => { if (!response.ok) throw new Error(`上传失败:HTTP状态码 ${response.status}`); console.log("文件上传成功"); }) .catch(err => console.error("上传异常:", err));
方案二:通过服务端Metadata定义元数据(适合固定元数据场景)
Lambda生成URL代码
const params={ Bucket: bucket, Key: key, Metadata:{ "filesize": filesInfo.fileSize.toString(), "origfilename": filesInfo.origFilename, // 服务端无需编码 "filetype": filesInfo.fileType, }, }; const command=new PutObjectCommand(params); const url = await getSignedUrl(s3Client, command, { expiresIn: expiry, // 无需指定signedHeaders,Metadata会自动纳入签名 });
客户端fetch代码
const myRequest = new Request(url, { method: 'PUT', mode: 'cors', cache: 'default', body: fileToSubmit, // 无需传递x-amz-meta-*请求头 }); fetch(myRequest) .then(response => { if (!response.ok) throw new Error(`上传失败:HTTP状态码 ${response.status}`); console.log("文件上传成功"); }) .catch(err => console.error("上传异常:", err));
内容的提问来源于stack exchange,提问作者robert smith
相关产品推荐
相关产品推荐

