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

使用预签名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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:39:22