网络知识 娱乐 对象存储COS跨域CORS问题小结

对象存储COS跨域CORS问题小结

跨源资源共享(CORS)

CORS(Cross-origin resource sharing) 中文名称"跨域资源共享",由于安全原因,Web 应用程序默认情况只能在同源(协议、域名和端口)的情况下向服务器获取数据。

主要有以下三种行为会受到限制:

Cookie、LocalStorage 和 IndexDB 无法读取。 DOM 无法获得。禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。 AJAX 请求不能发送(XMLHttpRequest)。

但是在日常的业务开发中,我们是需要经常访问跨域资源的。CORS 机制允许 Web 应用进行跨源访问,需要浏览器和服务器同时支持。整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

1. 请求分类

CORS 将请求分成了两类:简单请求(Simple Request)和非简单请求。其中非简单请求会触发预检请求(Preflight Request)。满足以下两大条件的请求就属于简单请求。

请求方法为下列方法之一 GET HEAD POST 请求的 HTTP 的头部信息不超出以下几种字段 Accept Accept-Language Content-Language Content-Type -> { text/plain, multipart/form-data, application/x-www-form-urlencoded }undefinedDPR Downlink Save-Data Viewport-Width Width

这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。

凡是不满足上面两个条件,就属于非简单请求。例如 COS V5 版本的 XML 接口中,当 Content-Type 为 application/xml 时就会触发 CORS 预检请求。

2. 简单请求

对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个 Origin 字段。下面我们先看一下 COS 服务器端对于跨域访问 CORS 设置中的各参数的配置作用,并给出结果图。

2.1 浏览器端

浏览器在发起跨域请求时会自动向 HTTP Header 添加一个额外的请求头字段:Origin。Origin 字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。

另外还有三个 Sec-Fetch-* 开头的字段,这是一个新的草案 Fetch Metadata Request Headers。

Sec-Fetch-Mode 代表请求的模式,主要有 cors、navigate、nested-navigate、no-cors 等等。

Sec-Fetch-Site 代表请求的来源是同源还是跨域。

2.2 COS 服务器端

Access-Control-Allow-Origin -> 来源 Origin

作用:COS 服务端允许跨域的源。

如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。

Access-Control-Allow-Methods -> 操作 Methods

作用:HTTP 请求方法的限制

这个参数应该还是比较容易理解的,允许浏览器的 CORS 请求会用到哪些 HTTP 方法。

Access-Control-Expose-Headers -> Expose-Headers

作用:允许浏览器端能够获取相应的 header 值

CORS 请求时,如果服务器端没有设置对应的Access-Control-Expose-Headers 字段,浏览器通过请求响应后的 Header 如下,比如我们非常熟悉的 x-cos-request-idETag 等头部无法在浏览器中无法获取到。

在 COS CORS 设置中把Expose-Headers置为*再来看一下结果

就可以拿到 COS 服务器端返回的全部 Header 字段了

Access-Control-Allow-Credentials

作用:是否允许发送 Cookie

这个头部在 COS CORS 设置中并没有对应的选项,如果要发送 Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。当 Access-Control-Allow-Origin 指定源后,COS 服务器端会自动设置该字段为 true。

当然,如果需要使用该特性,开发者也必须在请求时打开withCredentials属性。

3. 非简单请求

预检请求是在发送实际请求前,客户端先发送一次 OPTIONS 方法请求到服务器端来确认请求是否通过,可以避免跨域请求对服务器的用户数据造成影响。

如何判断是否会发送预检请求可以参考第一部分的请求分类。

3.1 浏览器端

预检请求用的请求方法是 OPTIONS,表示这个请求是用来询问的。

当然也需要带上 Origin 字段。

除了 Origin 字段,预检请求的头信息还包括两个特殊字段 Access-Control-Request-MethodAccess-Control-Request-Headers

Access-Control-Request-Method

该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法。如 PUT、POST、GET 等。

Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段。

如上图在请求的时候加上了自定义头部 X-Custom-Header = shuoweiwu,所以触发了预检请求。

Provisional headers are shown 字面意思是"显示了临时报文头",代表请求被阻塞,未收到响应,说明 请求并没有发出去

看下控制台报错和 Network 里的请求详情,是由于预检请求报错 403,被浏览器拦截了。

Access to XMLHttpRequest at 'https://xxxxxxxxx-xxxxxxxxxx.cos.ap-guangzhou.myqcloud.com/test/3.jpg' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

3.2 COS 服务器端

Access-Control-Allow-OriginAccess-Control-Allow-Methods 同简单请求一样,分别用于判定请求源和请求方法,需要注意的是以下两个配置项。

Access-Control-Allow-Headers -> Allow-Headers

作用:表示服务器允许请求中携带的请求头部字段。

比如上面预检请求中的 X-Custom-Header 头部。

Access-Control-Max-Age -> 超时 Max-Age

作用:指定本次预检请求的有效期,单位为秒。

如果设置 超时 Max-Age 为 0,则浏览器发送请求的时候始终都会先发送 OPTIONS 预检请求。

COS 中的 CORS 配置:

预检请求:

实际请求:

超时 Max-Age 设置为 600 时,只有在第一次请求时发送了 OPTIONS 预检请求。

4. 跨域重定向

当跨域请求被重定向时,中间服务器返回的 CORS 相关的响应头应当与最终服务器保持一致。 任何一级的 CORS 失败都会导致 CORS 失败。即需要满足每一级的 CORS 都能够通过验证。

浏览器会直接访问重定向后的地址,可以跟随多次重定向。但是需要注意的一点就是:

重定向后请求头 Origin 字段会被设为 null

重定向请求:

重定向后 Origin 字段被置为 null,导致重定向后的跨域请求失败:

解决方法有以下两种:

  1. 设置每一级的 Access-Control-Allow-Origin 字段为 *
  2. 设置重定向后的 Access-Control-Allow-Origin 字段为 null

5. 跨域的 src 属性

具有 src 属性的 HTML 标签都可以跨域

<link>,<script>,<img> 等标签是可以直接进行跨域访问的,但是不会产生跨域头。

6. 常见问题总结

当然这里最常见的问题就是已经配置好了跨域头,用 curl 测试生效,但是在前端页面访问的时候没有生效,看 Network 的请求头里确实是没有 CORS 的相关字段。

由于img标签是可以直接进行跨域访问的,在请求 COS 前,img标签加载了同样的图片,因为img加载在前,等到访问 COS 中的资源的时候,浏览器直接使用了缓存,缓存中是没有跨域头的,导致了跨域失败。

如何判定有可能是命中了浏览器缓存?

  1. 请求的时候存在 Provisional headers are shown字段,如上所述,代表请求没有发出来,有可能是命中了浏览器缓存。
  2. AJAX 请求的 COS 资源的 request id 与 img、script 标签等一致。
  3. Network 栏里的 Remote Address 为空或者 size 中的值为(disk cache)。
  4. 打开调试器的 disable cache 选项后观察看下能否复现。

使用场景以及命中浏览器缓存后的解决方案:

  1. 直接访问COS源站
    1. 使用Cache-Control头部关闭缓存。如在 COS 上传的时候加上该头部Cache-Control:no-cache,或者复制该资源的时候加上该头部。如果对象数量不是很多,可以直接在COS控制台点开该对象详情,设置自定义Headers。
    2. 设置 <img> 标签的 crossorigin 属性的值为 anonymous,强制图片每次请求都使用 XHR 的 CORS 请求。
    3. AJAX 请求图片的时候加上随机参数。
  2. 访问CDN域名,CDN回源到COS 如果只在COS侧配置了跨域,但是没有在CDN配置的话,由于CDN会缓存住第一次访问的请求,第一次请求没有跨域的话CDN会缓存住这个头部,可能会导致后面的跨域请求失败了,所以这种场景下建议在CDN侧下发跨域配置。还有一种场景是一个COS域名对应多个CDN域名时,也是由于CDN的缓存问题,可能会导致各个CDN域名表现不一致,这种场景也建议在CDN配置跨域头部。CDN 自定义响应头配置
    1. 仍然可以使用COS的Cache-Control头部关闭缓存,并且刷新对应的CDN的URL。
    2. 设置 <img> 标签的 crossorigin 属性的值为 anonymous,强制图片每次请求都使用 XHR 的 CORS 请求。
    3. AJAX 请求图片的时候加上随机参数。

ps: 其中设置 <img> 标签的 crossorigin 属性的方式是可以使用本地缓存的,但是可能有些浏览器是不支持 crossOrigin 的。

Vary头部 -> COS对跨域的进一步支持

Vary头部的使用场景是本地浏览器通过多个域名访问同一个URL,带上Vary头部后浏览器会缓存住不同Origin的请求,这个头部COS侧会尽快安排上,丰富产品的特性。

其他常见问题:

  • 重定向后跨域失败 -> 判断是否满足每一级的 CORS 验证
  • 浏览器无法获取到如ETag等字段 -> 参考上面 CORS 的 Expose Header 的配置

Reference:

跨源资源共享(CORS)

跨域资源共享 CORS 详解

✋?? CS Visualized: CORS

总结-使用 CORS 解决跨域问题