上周末我们系统做上线前联调,前端同事跑过来的时候脸色不太好看:“上传附件到华为云OBS一直报错,控制台红字刷了一屏,什么has been blocked by cors policy,是不是你桶的权限给我配错了?”我看了一眼报错,心里基本就有数了——又是CORS。说实话,对接华为云OBS做附件上传,十次联调里有八次会栽在这个CORS上,剩下的两次是签名和权限问题。这东西原理不复杂,但坑是真不少,尤其是前端直传场景,配置错了连个像样的错误提示都没有,全靠自己猜。
这篇文章我就结合这次实际对接的完整过程,把华为云OBS上传附件时CORS报错的来龙去脉、配置方法、常见坑位一次说清楚。不管你是后端被前端追着问,还是前端自己排查了半天没头绪,只要你负责对接OBS上传,这篇文章应该能帮你省下至少半天时间。
1. 先搞明白CORS到底是什么,为什么上传附件会栽在这里
1.1 从一次“莫名其妙”的报错说起
先说这次的实际现象。我们的场景是浏览器端直传附件到OBS,前端用的是华为云OBS的JavaScript SDK,流程大致是:后端先通过临时凭证接口签发一个带权限的临时AK/SK和securityToken,前端拿这个临时凭证初始化OBS客户端,然后直接把文件PUT到指定桶。这个方案在本地开发环境跑得好好的,一部署到测试环境就报错,错误信息是这样的:
text复制Access to XMLHttpRequest at 'https://bucket.obs.cn-north-4.myhuaweicloud.com/xxx/file.pdf'
from origin 'https://app.example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
前端同事的第一反应是“桶的权限是不是没配”,第二反应是“是不是后端签名没签对”。我检查了一圈,权限没问题、签名没问题、网络也没问题,最后把问题定位到了桶的CORS规则上——测试环境的桶压根没配置CORS规则,而本地环境用的是别的桶,配置是齐全的,所以一直没暴露。
这个案例特别典型,因为它反映了一个普遍认知偏差:很多人以为CORS是服务端接口层面的东西,跟对象存储这种“资源型”服务没关系。实际上,浏览器把OBS的PUT请求当作一次跨域XMLHttpRequest来处理,只要你的页面域名跟桶的访问域名不一致,就必须通过CORS机制来放行,否则浏览器会直接拦截响应,哪怕OBS服务器已经成功保存了文件。
1.2 同源策略与跨域的基本概念
CORS全称是Cross-Origin Resource Sharing,跨域资源共享。要理解它,得先理解浏览器的同源策略(Same-Origin Policy)。同源的定义是协议、域名、端口三者完全一致,比如https://app.example.com和https://api.example.com不是同源,http://和https://也不是同源,域名相同但端口不同同样不是同源。
同源策略是浏览器最基础的安全机制之一。它的存在意味着,https://app.example.com页面里的JavaScript代码,默认情况下不能自由读取https://bucket.obs.cn-north-4.myhuaweicloud.com这个域名下的资源响应。这个限制对普通图片加载、<script>标签引入是没有影响的,因为那些是标签发起的请求,不受同源策略约束。但XMLHttpRequest和fetch发起的请求是严格受限的。
这里有个容易混淆的点:同源策略限制的是“读取响应”,不是“发出请求”。也就是说,你的跨域请求其实发出去了,服务器也收到了,甚至文件可能已经传上去了,但浏览器拿到响应后发现没有携带允许跨域读取的响应头,就会把响应“扔掉”,并且在控制台报CORS错误。这就是为什么有时候你看到CORS报错,但去OBS桶里一看,文件居然已经传上去了。第一次遇到这个情况的人真的会一脸懵。
1.3 简单请求和预检请求到底怎么区分
CORS机制下,跨域请求被分为两类:简单请求(Simple Request)和预检请求(Preflight Request)。
简单请求需要同时满足以下条件:
- 请求方法只能是GET、HEAD、POST
- 请求头只能包含一些安全的头部字段,比如Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(且Content-Type的值仅限于
application/x-www-form-urlencoded、multipart/form-data、text/plain) - 不能在XMLHttpRequest中使用
XMLHttpRequestUpload对象注册事件监听器 - 请求中没有使用`Rea
