You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 00:05:58