浏览器直传 OBS 报 CORS 或 403:按 OPTIONS 预检、CORS 规则、签名六步排查
把图片从浏览器直接传到对象存储 OBS 时,控制台里最常见的一类报错是 No 'Access-Control-Allow-Origin' header is present,或者预检请求 OPTIONS 直接返回 403。这两类现象大多出在跨域(CORS)规则上,但也可能根本不是跨域问题。预检和签名是两类问题,混在一起改配置只会越改越乱,所以排查的顺序很重要。
一、先确认失败的是预检还是正式请求
打开开发者工具的 Network 面板,看失败的是 OPTIONS 请求,还是后面的 PUT/POST。
浏览器在跨域且带自定义请求头时会先发一次预检,比如 Content-Type: application/json、Authorization 或 x-obs-* 头。预检不通过,正式请求根本不会发出,服务端日志里也就看不到任何上传记录——这是很多人排查时走偏的原因:盯着服务端日志找上传失败,其实请求压根没到。
二、用 curl 模拟预检请求
curl -i -X OPTIONS "https://.obs..myhuaweicloud.com/test.jpg" \
-H "Origin: https://www.example.com" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: content-type"
正常应返回 200,并带上 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers。如果返回 403,或者没有这几个响应头,说明桶上没有任何一条规则匹配这次请求的来源、方法和头域。
三、逐项核对桶的 CORS 规则
在 OBS 控制台进入目标桶,找到「权限管理」下的「CORS 规则」,逐项对照:
- 允许来源:必须和浏览器地址栏里的 Origin 完全一致,包括协议和端口。
https://www.example.com、http://www.example.com、https://www.example.com:8080是三个不同的来源。测试阶段可以先写*,上线前收紧。 - 允许方法:上传用到的
PUT、POST要勾上,读取用GET、HEAD。多段上传还会用到POST和PUT的组合,别只勾一个。 - 允许头域:要覆盖前端实际发送的所有头。用了
Content-Type就写Content-Type,用预签名之外的鉴权就要包含Authorization,用到x-obs-acl之类的头也要列出来。测试时可以先填*。 - 暴露头域:前端要读取响应头时必须声明。分段上传需要读取每一段的
ETag,如果没有把ETag加到暴露头域,浏览器里拿到的就是空值,合并分段时会报错。
保存后,用第二步的 curl 再验证一遍。
四、中间有 CDN 时先看缓存
如果域名走了 CDN 加速,浏览器访问的是 CDN 节点而不是 OBS。CDN 可能缓存了一份没有 CORS 头的响应,导致规则明明配了却一直不生效。需要在 CDN 配置里为静态资源添加跨域相关的响应头,刷新缓存后重试。
判断方法:把 curl 的地址换成 OBS 源站域名。源站有头、CDN 域名没有头,问题就在 CDN。
五、CORS 正常了仍 403,转去查签名
CORS 通过但上传仍然 403,这时要看响应体里的错误码,而不是继续改跨域:
SignatureDoesNotMatch:预签名 URL 生成时的Content-Type和浏览器实际发送的不一致,是最常见的原因。生成签名时用什么头,前端必须原样带什么头。RequestTimeTooSkewed:客户端或服务器时间与标准时间相差过大,校准系统时间即可。- URL 已过期:预签名链接有有效期,前端缓存了旧链接就会失败,每次上传前重新获取。
AccessDenied:签名所用账号没有该桶的写权限,检查桶策略与 IAM。
六、自检顺序
先看失败的是不是 OPTIONS,再用 curl 复现预检,核对来源、方法、头域;需要读响应头就补暴露头域;有 CDN 先比较源站与 CDN 的响应头;最后才查签名与权限。
大多数问题在前三步就能定位。
信源: