使用预签名URL向S3 PUT文件时遭遇403 SignatureDoesNotMatch错误
搞定S3预签名PUT URL上传的SignatureDoesNotMatch错误
我来帮你解决这个头疼的问题!你遇到的403签名不匹配,核心原因就是生成预签名URL时的参数和前端实际请求的参数/headers完全不一致——S3对签名的校验非常严格,哪怕一个header大小写不对都会报错。
第一步:先把后端预签名URL的生成逻辑改对
首先你代码里的Key是硬编码的123456,这样会导致后面上传的文件直接覆盖之前的,先改成动态生成唯一Key。另外,ContentType的传递要注意前后端参数名统一,别搞混了:
var aws = require('aws-sdk'); // Request presigned URL from S3 exports.S3presignedURL = function (req, res) { var s3 = new aws.S3(); // 生成唯一Key,比如用时间戳加随机字符串,避免文件覆盖 const uniqueKey = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; var params = { Bucket: process.env.S3_BUCKET, Key: uniqueKey, // 这里要和前端传过来的参数名对应,比如前端传contentType,就用req.body.contentType ContentType: req.body.contentType, // 如果需要上传后文件公开访问,就打开这个ACL配置 // ACL:'public-read', // 可以自定义过期时间,默认15分钟,这里设成1小时示例 Expires: 3600 }; s3.getSignedUrl('putObject', params, function(err, url) { if(err) { console.error('生成预签名URL出错:', err); return res.status(500).json({error: 'Failed to generate presigned URL'}); } // 把生成的Key也返回给前端,方便后续记录文件路径 res.json({url: url, key: uniqueKey}); }); };
划重点:后端生成URL时指定的
ContentType,必须和前端PUT请求的Content-Type完全一致,比如image/jpeg和image/jpg是两个不同的值,一定要统一!
第二步:修复前端ngFileUpload的上传代码
你之前遇到的Content-Type: undefined,是因为ngFileUpload的参数格式用错了。S3的PUT预签名URL要求直接上传文件内容,不能用multipart/form-data格式,所以别把文件嵌套在data.file里,直接传文件对象就行:
Upload.upload({ url: response.data.url, // 后端返回的预签名URL method: 'PUT', headers: { // 这里的Content-Type必须和后端生成URL时用的一模一样 'Content-Type': file.type }, data: file // 直接传文件对象,不要嵌套! }) .then(function (resp) { console.log('文件上传成功:', resp.config.data.name); }) .catch(function (resp) { console.error('上传失败:', resp); });
之前的问题就是因为你把文件放在
data.file里,ngFileUpload自动处理成了multipart格式,导致Content-Type被覆盖或者未正确设置,进而签名不匹配。
第三步:验证几个关键一致性
一定要确保这几点完全匹配:
- 后端生成URL时的
ContentType和前端请求的Content-Type(包括大小写和格式) - 如果后端设置了
ACL: 'public-read',前端请求头必须加上x-amz-acl: public-read;如果后端没设置,前端绝对不能加这个头 - 预签名URL的过期时间是后端生成时指定的,前端不需要在请求头里加
expires字段
最后:简化你的CORS配置
你的CORS配置已经没问题了,但可以简化一下,因为AllowedHeader: *已经包含了所有headers,不需要重复列Content-Type和Authorization:
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedOrigin>http://localhost:9500</AllowedOrigin> <AllowedOrigin>https://localhost:9500</AllowedOrigin> <AllowedOrigin>http://www.example.com</AllowedOrigin> <AllowedOrigin>https://www.example.com</AllowedOrigin> <AllowedOrigin>http://lvh.me:9500</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <ExposeHeader>ETag</ExposeHeader> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
按照上面的步骤改完,应该就能解决签名不匹配的问题了!
内容的提问来源于stack exchange,提问作者honkskillet
相关产品推荐
相关产品推荐

