OSS 签名报 403 的常见原因:Date 头与资源路径的坑

CLARA轻量论坛系统
CLARA轻量论坛系统 星耀SVIP星耀SVIP管理员管理员 黑卡会员黑卡会员
发布于 2026-09-28 20:00 ·4 浏览 ·0 回复

看完这篇,你能在十分钟内自己判断 OSS 签名 403 到底是 Date 头写错了,还是资源路径对不上,而不是反复换 SDK、反复贴同一段代码问人。

第一步:先看清 403 里的错误码,别急着改代码

403 只是一个 HTTP 状态码,OSS 返回的 XML 里才有真答案。用 curl 打一次请求,看 `<Code>` 字段:

curl -i "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/test.txt" -H "Authorization: OSS ..."
  • `SignatureDoesNotMatch`:签名算错了,本文主角,继续往下看。
  • `RequestTimeTooSkewed`:客户端时间和 OSS 服务器差了超过 15 分钟,直接查时间同步。
  • `AccessDenied`:签名其实是对的,是权限、IP 白名单、Referer 或防盗链拦的,别浪费时间改签名。
  • `InvalidAccessKeyId`:AccessKeyId 不存在或被禁用。

注意:不要拿 403 的 status code 当唯一线索。同一个 403 背后有四种完全不同的原因,错误码不同,改的方向完全不同。

第二步:Date 头的四个坑

签名串里的 Date,必须和你 HTTP 请求头里实际发送的 Date 完全一致。常见错误:

1. 格式写错。 必须是 HTTP-date 格式的 GMT 字符串,例如:

Wed, 21 Oct 2015 07:28:00 GMT

不是 `2025-01-01 12:00:00`,不是 ISO8601 带毫秒,也不是本地时间不带时区后缀。

2. 时区没转 UTC。 服务器在 +08:00,直接 `date` 出来的是北京时间,直接塞进签名就是错的。生成时用:

date -u '+%a, %d %b %Y %H:%M:%S GMT'

3. 客户端和服务器时间漂移。 虚拟机挂起后恢复、Docker 容器没继承宿主时间、树莓派没装 RTC,都会导致时间慢几分钟到几小时。检查:

date -u
ntpdate -q ntp.aliyun.com

注意:不能只把本地时间「手动调准一次」。容器重启、宿主迁移后还会漂,正确做法是让宿主机跑 NTP/chrony,容器共享宿主时间。

4. 中间层把 Date 头改了。 Nginx 反代、CDN、API 网关默认会自己补一个 `Date` 响应头,某些代理也会覆写请求头。如果你的服务端在代理后面,先直连 OSS 复现一次,确认是不是代理动的手。

另外,用 V4 签名时会用 `x-oss-date` 头。如果你同时传了 `Date` 和 `x-oss-date`,两者不一致,照样 403。

第三步:资源路径(CanonicalizedResource)的五个坑

签名串最后一段是资源路径,规则是「跟请求实际走的那条路一致」,不是「你觉得它应该是什么」。

1. Bucket 前缀要不要带。 走路径风格 `oss-cn-hangzhou.aliyuncs.com/bucket/object` 时,签名里要带 `/bucket/object`;走 bucket 域名或 CNAME 自定义域名时,规则不同(CNAME 场景通常不带 BucketName)。最稳的验证方式是:用官方 SDK 生成一次同样的请求,把它内部拼出的 StringToSign 打印出来,和自己的对比。

2. 是否 URL 编码。 这是最高频的坑:URL 里必须出现 `%E4%B8%AD` 这类编码,而签名串里用的是原始 ObjectKey 还是编码后的,取决于你用的是 V1 还是 V4 签名,两者规则不同。混着抄示例代码,必错。

3. 空格和加号。 路径里的空格要编码成 `%20`,写成 `+` 就变成另一个 key 了。

4. 多余和缺失的斜杠。 ObjectKey 是 `a/b.txt`,签名里写成 `//a/b.txt` 或漏掉前导斜杠,都会 SignatureDoesNotMatch。大小写也一样,OSS 的 key 是大小写敏感的。

5. Query 参数没参与签名。 `?acl`、`?uploads`、`?response-content-disposition=...` 这类子资源和覆盖响应头参数,必须按规则参与签名,少一个就报错。

注意:如果你的请求经过自建 Nginx 转发,检查 `location` 和 `proxy_pass` 末尾的斜杠。`proxy_pass http://oss/;` 和 `proxy_pass http://oss;` 传给后端的路径不一样,会直接改写资源路径,签名自然对不上。

第四步:把 StringToSign 打出来,一眼定位

阿里云 V1 签名的原始串结构是:

VERB \n
Content-MD5 \n
Content-Type \n
Date \n
CanonicalizedOSSHeaders
CanonicalizedResource

写个十几行的脚本,把这段字符串打印出来,再手动 curl 一次带上相同的头,两边一比就知道差在哪。排查时按这个顺序看:Date 自己算的高频错,然后是资源路径的编码,最后才是 Content-Type 和 Content-MD5 是否和实际请求头一致。

注意:Content-Type 和 Content-MD5 也在签名串里。你签名时写了 `text/plain`,实际请求头却是 `application/octet-stream`,一样 403,而且报的还是 SignatureDoesNotMatch。

小结

  • 403 先看 XML 里的 Code,`SignatureDoesNotMatch` 才去查签名,`RequestTimeTooSkewed` 直接对时,`AccessDenied` 查权限。
  • Date 必须 GMT 格式、UTC 时区、和请求头一致,客户端与服务器时间差不能超 15 分钟。
  • 资源路径要和实际请求走的域名、Bucket 前缀、编码方式完全对应,多一个斜杠都不行。
  • Query 里的子资源参数必须参与签名。
  • 最快的定位办法:打印 StringToSign,和一次手动 curl 请求对照。
本文转载自 Clara轻量论坛系统 - 轻量级 PHP 论坛系统,原文地址:https://www.leleweb.cn/thread-630.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~