西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → S3 兼容存储对接踩坑:预签名 URL 报 403 的 4 个原因

S3 兼容存储对接踩坑:预签名 URL 报 403 的 4 个原因

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 8:54:33字体大小:A-A+

作者:xiix点击:0次评论:0次标签: S3兼容存储 预签名URL 对象存储 403报错 MinIO

  • 类型:服务器区大小:1.1M语言:中文 评分:10.0
  • 标签:
立即下载

这两年公司里的图片、视频、报表全往对象存储上搬,我们没有直接用云厂商的 S3,而是自己在一台大内存服务器上搭了一套 S3 兼容的存储网关,对外用同一套 SDK 跑本地和云上两套后端。刚开始挺顺,直到有天业务方反馈:同一个接口,本地好好的,连到云端就 403;偶尔又变成 404,说对象不存在,但明明用控制台能看见。折腾了大半天,最后发现问题不在存储本身,而在「预签名 URL」这个细节上。

如果你也在自建或对接 S3 兼容存储,需要一台大内存、大硬盘的机器先把环境跑起来,可以先看看这两家的报价和技术说明:itldc(全球云服务器,NVMe/大容量存储方案),ethernetservers(全球云服务器,NVMe/SSD 存储机型)

一、403 最常见的原因:签名和请求头对不上

S3兼容存储预签名URL对接调试场景

预签名 URL 的本质是「把一次带签名的 GET 请求固化在 URL 里」,服务端靠 URL 上的 X-Amz-Signature、X-Amz-Credential、X-Amz-SignedHeaders 三样东西判断放不放行。这里有个很容易踩的坑:签名里头的 SignedHeaders 记录了你生成签名那一刻用到的请求头列表,但客户端真正发起请求时如果多带或少带了一个头,签名立刻对不上,直接回 403 SignatureDoesNotMatch。

最典型的是 Content-Type。有些 SDK 生成签名时默认没把 Content-Type 计入 SignedHeaders,你用 curl 或别的工具去请求时手贱加了个 -H "Content-Type: application/octet-stream",服务端就认为签名覆盖的请求头和实际的不一致。解决办法是生成签名时显式声明参与签名的请求头,或者干脆不要加这个头。另外 Region 也要对齐,URL 里的 region 必须和密钥所属 region 一致,写成 us-east-1 但密钥是 ap-east-1,一样 403。

二、时间戳过期和时钟偏移

预签名 URL 有过期时间,默认 1 小时,也有 7 天、15 天这种长的。超过 X-Amz-Expires 就会变成 403 Request has expired。这个看起来简单,但我们在自建那套上遇到过更隐蔽的一种:机器时间不准。签名用的是客户端本地的 UTC 时间戳,服务端拿自己的时间一比,发现这个时间戳比服务端「未来」多了几分钟,于是判定无效。我那台机器因为 BIOS 电池没电,每次重启时间就回退到 2019 年,签名出来的东西在服务端看来是穿越的,直接拒。

所以排查 403 时,先在两台机器上跑一遍 date -u 对一下,不要一上来就怀疑权限策略。差个几分钟,问题就定位到了。

三、404:桶名、路径风格和分片对象

对象存储桶与文件上传示意图

404 相对好判断,分两种情况。第一种是对象真的不存在,比如你把路径里的斜杠写成了反斜杠,或者大小写不对S3 的 key 是大小写敏感的,Photo/IMG_1.jpg 和 photo/img_1.jpg 是两个完全不同的对象。第二种情况更坑:桶名写对了、key 也对,但请求打到了另一个 endpoint 上。很多自建网关同时开了 path-style(https://host/bucket/key)和 virtual-host style(https://bucket.host/key)两种访问方式,如果客户端配置和实际开启的模式不一致,网关就会把请求当成桶不存在,返回 404 NoSuchBucket,而不是真正的 NoSuchKey。

还有一类 404 来自分片上传。PUT 上去的 multipart object 在 complete 之前,是不作为一个完整对象存在的,用普通的 GET 去查当然查不到,只能通过 ListMultipartUploads 看。这种情况下要查的是 ListMultipartUploads 的残留,而不是文件丢了。

四、权限策略写得太死

403 还有一种来源是策略本身。我们最早图省事,给应用配的策略只有 s3:GetObject,没有 GetObjectVersion。结果对象被覆盖过一次之后,带版本号的老对象直接取不到,日志里还是那个 SignatureDoesNotMatch 之外的其他拒绝码。如果你的业务需要版本管理或者从备份恢复,得把 s3:GetObjectVersion、s3:ListBucket 这些都算进去。还有一点容易忽略:ListBucket 这个动作的 Resource 要写桶 ARN,GetObject 要写桶内对象 ARN,写反了也会被拒。

五、预签名 URL 本身也有个安全坑

存储服务器与客户端内网架构示意

最后说说安全侧的事。预签名 URL 一旦生成,在有效期内等于「免登录凭证」,谁拿到都能访问。所以有效期别一上来就设成 7 天甚至更长,按业务实际需要给,图片 CDN 那种场景 5 分钟就够。另外别把预签名 URL 打到日志里、也别放在 Referer 里跳转,我们排查时发现过一条 nginx 访问日志把完整签名 URL 记下来了,那条日志等于半个凭证。稳妥的做法是服务端中转,或者给 URL 加上 IP 白名单和绑定条件。

六、我的排查顺序

工程师排查403报错现场

总结一下我自己的一套顺序,遇到问题时基本都能快速收敛:先看响应体里的 Code 字段(SignatureDoesNotMatch / Request has expired / AccessDenied / NoSuchKey / NoSuchBucket,指向的问题完全不同);再对时间;对 region 和 endpoint;对 SignedHeaders 和实际请求头;最后才去看桶策略和权限。这一套跑完,403 和 404 基本都能落到具体那一条上。

另外补一句,选型的时候如果确定要做 S3 兼容存储,底层机器的磁盘、内存和网卡要留够冗余,因为对象存储真正吃 IOPS 的地方是小文件海量读写;反过来,如果你只是要一个能放静态资源的桶,其实完全没必要自己维护这一套,选现成的更省事。适合自己的才重要。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲