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
2
3
4
5
6
7
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<Key>68c1390a.jpg</Key>
<BucketName>bucketname</BucketName>
<Resource>/bucketname/68c1390a.jpg</Resource>
</Error>

初步分析

错误提示是 签名不匹配。考虑到签名 URL 是在后端用 AWS S3 客户端生成的,代码逻辑没有改动,因此怀疑是 代理转发时导致的请求变化

深入理解 S3 签名机制

S3 V4 签名在计算时,会严格依赖以下请求信息:

  • Host:请求头里的 Host 必须与签名时一致
  • URI Path:路径必须一致,不能多也不能少
  • Scheme (协议):http/https 不同也会导致签名差异
  • Signed Headers:只要包含在 X-Amz-SignedHeaders 里的头部,值都必须完全匹配

因此,哪怕 Nginx 在转发时稍微改动了路径、Host 或协议,MinIO 后端重新计算签名时就会发现不一致,从而报错。

Nginx 配置的问题

最初的代理配置如下:

1
2
3
location /oss/ {
proxy_pass http://192.168.2.101:9000/bucketname/;
}

问题有两点:

  1. Host 不一致
    后端签名时的 URL 是 192.168.2.101:9000,但请求经过 Nginx 时 Host 变成了 oss.example.com

  2. 协议不一致
    签名用的是 http,外部请求是 https,MinIO 在校验时自然会不一致。

解决方案

最终的 Nginx 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 文件服务代理
location /oss/ {
proxy_pass http://192.168.2.101:9000/bucketname/;

# 重点:Host 必须与签名时一致
proxy_set_header Host 192.168.2.101:9000;

# 重点:协议必须与签名时一致(这里签名用的是 http)
proxy_set_header X-Forwarded-Proto http;

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";

chunked_transfer_encoding off;
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
}

配置解析

  • 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,成功返回文件。

经验总结

  1. S3 签名严格性
    预签名 URL 的校验非常敏感,任何请求细节变化都会导致签名不匹配。

  2. 保持一致是关键

    • Host 要一致
    • 协议要一致
    • 路径要一致
  3. 代理层最好透明转发
    如果有条件,尽量避免修改路径或域名映射,否则需要在代理层额外处理。

请求流向图

1
2
3
4
5
6
7
8
9
sequenceDiagram
participant C as Client (oss.example.com/oss/...)
participant N as Nginx (oss.example.com)
participant M as MinIO (192.168.2.101:9000)

C->>N: 请求对象 (GET https://oss.example.com/bucketname/file.jpg)<br/>携带预签名URL
N->>M: 转发请求<br/>(GET http://192.168.2.101:9000/bucketname/file.jpg)<br/>Host=192.168.2.101:9000
M-->>N: 返回对象 (200 OK, file.jpg)
N-->>C: 返回对象 (200 OK, file.jpg)