Lambda生成S3预签名POST URL上传报SignatureDoesNotMatch错误
S3 预签名POST上传触发SignatureDoesNotMatch问题定位与修复
核心错误根源
你完全混淆了S3两类预签名上传的协议规范,把generate_presigned_post()对应的POST表单上传逻辑,错用成了预签名URL上传的参数拼接方式,导致S3端计算的校验签名和你提交的内容完全不匹配。
具体错误点
- URL拼接逻辑完全错误
generate_presigned_post()返回的url字段本身就是POST请求的目标地址,不需要在后面拼接对象Key,也不需要把签名、凭证类参数拼到URL的查询串里。你当前代码里手写拼接带一堆X-Amz-*参数的URL路径,本身就不符合S3 POST预签名的请求结构要求。 - 漏提交必填的表单字段
你当前的FormData里只上传了文件本身,没有把返回的fields对象里的所有键值对(包括key、x-amz-algorithm、x-amz-credential、x-amz-date、x-amz-security-token、x-amz-signature、policy)作为表单项提交。POST预签名的规则要求所有凭证类字段必须放在请求体的表单段中,而不是URL查询参数里,字段缺失自然无法通过签名校验。 - 表单字段提交顺序错误
即便把所有字段都加入FormData,也必须注意提交顺序:所有预签名返回的fields字段要在文件字段之前append,文件字段必须是FormData里最后一个表单项,否则S3解析表单的时候无法匹配policy定义的字段顺序,也会触发签名不匹配错误。 - 代码里手动注释了policy字段的传递逻辑,这个字段是POST预签名的核心校验依据,缺少它S3根本无法回溯你生成签名时的规则定义。
- 多余的类型转换逻辑
根据你给出的接口返回结构,signed_url字段本身是包含url和fields的对象,不是序列化后的字符串,你代码里执行JSON.parse(data["signed_url"])会直接触发类型转换错误,即便侥幸拿到值,也是解析出的错误结构。 - 对
x-amz-security-token字段手动做了encodeURIComponent编码,SDK生成的字段值本身已经符合表单提交的格式要求,过度编码会导致提交的值和生成签名时的原始值不一致,也会触发签名不匹配。
正确实现方式
Lambda侧的generate_presigned_post()调用逻辑不需要修改,只需要调整客户端的上传逻辑即可,正确代码参考如下:
function uploadToS3(data){ const form = new FormData(); // 直接取返回的POST目标URL,不做任何拼接、JSON.parse处理 const postUrl = data["signed_url"]["url"] const fields = data["signed_url"]["fields"] // 先把所有预签名返回的字段逐个加入FormData,不做手动编码 Object.keys(fields).forEach(key => { form.append(key, fields[key]) }) // 最后再把文件加入FormData,保证是最后一个表单项 const uploadFile = $('#customFile1').get()[0].files[0] form.append("file", uploadFile) $.ajax({ type : 'POST', url: postUrl, data: form, processData: false, contentType: false, success:function(resp){ console.log("上传成功", resp) }, error:function(resp) { console.log("上传失败", resp); } }) }
额外注意事项
ExpiresIn=30的过期时间非常短,要注意客户端拿到凭证后立刻发起上传,不要留过长的用户操作等待时间,否则会触发凭证过期错误。- 如果你的S3桶开启了强制SSL访问,要确保返回的
url字段是https开头的地址,避免因为协议不匹配触发访问拒绝。
内容的提问来源于stack exchange,提问作者user12066563
相关产品推荐
相关产品推荐

