MinIO 预签名 URL 报错 SignatureDoesNotMatch 的排查复盘
背景
我们的文件服务基于 MinIO 搭建,文件访问走的是预签名 URL。
后端使用 AWS S3 SDK 生成签名,例如:
1 | http://192.168.2.101:9000/bucketname/68c1390a.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&... |
在内部环境访问一切正常。
但生产环境要求通过统一域名出口,因此我们在 Nginx 上加了一层代理,提供给外部系统的 URL 是:
1 | https://oss.example.com/oss/68c1390a.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&... |
上线后,用户反馈文件全部访问失败,返回错误如下:
1 | <Error> |
初步分析
错误提示是 签名不匹配。考虑到签名 URL 是在后端用 AWS S3 客户端生成的,代码逻辑没有改动,因此怀疑是 代理转发时导致的请求变化。
深入理解 S3 签名机制
S3 V4 签名在计算时,会严格依赖以下请求信息:
- Host:请求头里的
Host
必须与签名时一致 - URI Path:路径必须一致,不能多也不能少
- Scheme (协议):http/https 不同也会导致签名差异
- Signed Headers:只要包含在
X-Amz-SignedHeaders
里的头部,值都必须完全匹配
因此,哪怕 Nginx 在转发时稍微改动了路径、Host 或协议,MinIO 后端重新计算签名时就会发现不一致,从而报错。
Nginx 配置的问题
最初的代理配置如下:
1 | location /oss/ { |
问题有两点:
-
Host 不一致
后端签名时的 URL 是192.168.2.101:9000
,但请求经过 Nginx 时Host
变成了oss.example.com
。 -
协议不一致
签名用的是http
,外部请求是https
,MinIO 在校验时自然会不一致。
解决方案
最终的 Nginx 配置如下:
1 | # 文件服务代理 |
配置解析
- location + proxy_pass:直接将
/oss/xxx.jpg
映射到/bucketname/xxx.jpg
。 - proxy_set_header Host:强制使用内网地址
192.168.2.101:9000
,和签名时完全一致。 - proxy_set_header X-Forwarded-Proto:告诉后端这是
http
请求,避免 https/http 差异。
调整后,再次访问预签名 URL,成功返回文件。
经验总结
-
S3 签名严格性
预签名 URL 的校验非常敏感,任何请求细节变化都会导致签名不匹配。 -
保持一致是关键
- Host 要一致
- 协议要一致
- 路径要一致
-
代理层最好透明转发
如果有条件,尽量避免修改路径或域名映射,否则需要在代理层额外处理。
请求流向图
1 | sequenceDiagram |