ASP.NET Core 6 MVC Ajax通过AWS预签名URL上传S3失败求助
问题:通过预签名URL上传文件到AWS S3返回403 Forbidden
环境与实现代码
.NET Core 生成预签名URL代码
var urlString = string.Empty; var uniqueFileName = $"{Guid.NewGuid()}{Path.GetExtension(model.FileName)}"; try { AWSConfigsS3.UseSignatureVersion4 = true; var request = new GetPreSignedUrlRequest() { BucketName = "bucketname", Key = $"upload/{uniqueFileName}", Expires = DateTime.UtcNow.AddHours(2), ContentType = "video/mp4", Verb = HttpVerb.PUT }; request.Metadata.Add("firebaseId", fireBaseUser.Uid); urlString = await Resolver.AmazonS3Client.GetPreSignedURLAsync(request); } catch (AmazonS3Exception ex) { Console.WriteLine($"Error:'{ex.Message}'"); var modelReturn569 = new { status = 500, url = urlString, contentType = "video/mp4" }; return Json(modelReturn569); } var modelReturn56 = new { status = 200, url = urlString }; return Json(modelReturn56);
JavaScript 上传代码
function getAwsUrl(filename, contentType) { var obj = { fileName: filename, contentType: contentType }; return new Promise((resolve, reject) => { $.ajax({ type: "POST", url: '@Url.ActionLink("GetAwsUrl", "Account", new { }, "https")', data: JSON.stringify(obj), dataType: "json", headers: { "RequestVerificationToken": "@GetAntiXsrfRequestToken()" }, contentType: "application/json", success: function (data) { resolve(data); }, error: function (req, status, error) { reject(error); } }); }); } function uploadToAws(dataIncoming) { console.log(dataIncoming.url); var theFormFile = $('#videoUploader').get()[0].files[0]; return new Promise((resolve, reject) => { $.ajax({ type: "PUT", url: dataIncoming.url, processData: false, contentType: "video/mp4", data: theFormFile, success: function (data) { resolve(data); }, error: function (req, status, error) { reject(error); } }); }); }
调用逻辑
getAwsUrl($("#videoUploader")[0].files[0].name, "video/mp4") .then((data) => { uploadToAws(data).then((dataUploaded) => { alert("uploaded"); }).catch((error) => { console.log(error); }); }) .catch((error) => { iziToast.error({ position: 'bottomRight', pauseOnHover: false, displayMode: 2, layout: 2, message: "" }); })
错误信息
PUT https://s3.eu-west-2.amazonaws.com/bucketname/upload/27492328-4b7e-420f-b3d5-b047f3d5d1a6.mp4?X-Amz-Expires=7200&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA4VX7VVP3LMLE3CHF%2F20240515%2Feu-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240515T151600Z&X-Amz-SignedHeaders=content-type%3Bhost%3Bx-amz-meta-firebaseid&X-Amz-Signature=605eeb5654fbb822dd70cbfd773bad4fe027d65b65efc4baac8ae6e3342c52ce 403 (Forbidden)
S3 CORS配置
[{ "AllowedHeaders": ["*"], "AllowedMethods": ["GET","HEAD","POST","PUT"], "AllowedOrigins": ["*"], "ExposeHeaders": [], "MaxAgeSeconds": 3000 }]
解决方案
1. 补充前端请求的自定义元数据头
从预签名URL的X-Amz-SignedHeaders参数可以看到,签名包含了x-amz-meta-firebaseid,但当前前端PUT请求未携带该头,导致签名验证失败。
步骤1:修改后端返回模型,携带firebaseId
var modelReturn56 = new { status = 200, url = urlString, firebaseId = fireBaseUser.Uid }; return Json(modelReturn56);
步骤2:修改前端上传函数,添加对应请求头
function uploadToAws(dataIncoming) { console.log(dataIncoming.url); var theFormFile = $('#videoUploader').get()[0].files[0]; return new Promise((resolve, reject) => { $.ajax({ type: "PUT", url: dataIncoming.url, processData: false, contentType: "video/mp4", // 添加预签名时指定的自定义元数据头 headers: { "x-amz-meta-firebaseid": dataIncoming.firebaseId }, data: theFormFile, success: function (data) { resolve(data); }, error: function (req, status, error) { reject(error); } }); }); }
2. 验证IAM权限配置
生成预签名URL的AWS账号/角色需具备s3:PutObject权限,且权限范围覆盖目标存储桶及上传路径。示例IAM策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucketname/upload/*" } ] }
3. 检查服务器时间同步
AWS签名依赖UTC时间,若生成预签名的服务器时间与AWS时间偏差超过5分钟,会导致签名无效。确保服务器开启NTP时间同步服务。
4. 确认Content-Type完全匹配
在浏览器开发者工具的Network面板中,检查PUT请求的Content-Type头是否与预签名时的video/mp4完全一致,避免大小写、额外字符等差异。
内容的提问来源于stack exchange,提问作者ahmet simsek
相关产品推荐
相关产品推荐

