跳到主要内容

CORS 跨域方案

2024年03月20日
柏拉文
越努力,越幸运

一、认识


跨源资源共享 Cross-origin resource sharingCORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。

跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的预检请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。

二、工作


2.1 简单请求

Cors规范定义简单请求的原则是: 请求不是以更新(添加、修改和删除)资源为目的,服务端对请求的处理不会导致自身维护资源的改变。对于一个请求,必须同时符合如下要求才被划为简单请求规则:

  1. Http Method只能为其一

    • GET
    • POST
    • HEAD
  2. 请求头只能在如下范围

    • Accept

    • Accept-Language

    • Content-Language

    • Content-Type,其中它的值必须如下其一:

      • text/plain

      • multipart/form-data

      • application/x-www-form-urlencoded

服务端需要设置的响应头

  • Access-Control-Allow-Origin(必含)

  • Access-Control-Allow-Credentials(可选): 如果简单请求有传Cookie,那么需要设置Cookie

2.2 预检请求

非简单请求: 简单请求之外的都为非简单请求,也称为复杂请求。比如说需要发送 PUTDELETEHTTP动作,或者发送Content-Type:application/json的内容或者发送自定义请求头

非简单请求可能对服务端资源改变,因此 Cors 规定浏览器在发出此类请求之前必须有一个**预检(Preflight)**机制,这也就是我们熟悉的 **OPTIONS**请求。

Preflight预检机制

顾名思义,它表示在浏览器发出真正请求之前,先发送一个预检请求,这个在Http里就是 OPTIONS请求方式。这个请求很特殊,它不包含主体(无请求参数、请求体等),主要就是将一些凭证授权相关的辅助信息放在请求头里交给服务器去做决策。

Preflight预检机制请求头

Preview
  • Origin

  • Access-Control-Request-Method

  • Access-Control-Request-Headers: 由content-type自定义请求头组成

服务端需要设置的响应头

  • Content-Type

  • Access-Control-Allow-Methods

  • Access-Control-Allow-Headers

  • Access-Control-Allow-Origin(必含)

  • Access-Control-Allow-Credentials(可选): 如果简单请求有传Cookie,那么需要设置Cookie

Preflight预检机制预检过程

  1. 服务端收到 Preflight OPTIONS 请求,允许预检请求通过,返回个200,并且响应头中包含服务端设置的响应头信息。如果不处理**Preflight OPTIONS 请求**,那么浏览器直接会显示404

  2. 复杂请求先通过自己的 Origin匹配预检响应中的 **Access-Control-Allow-Origin**的值,若不匹配就结束请求,若匹配就继续下一步验证

  3. 拿到预检响应中的 Access-Control-Allow-Methods头。若此头不存在,则进行下一步,若存在则校请求头Access-Control-Request-Method的值是否在此列表中,在其内继续下一步,否则失败

  4. 拿到预检响应中的**Access-Control-Request-Headers头。同 请求头 中的**Access-Control-Allow-Headers` 值进行比较,全部包含在内则匹配成功,否则失败

  5. 以上全部匹配成功,就代表预检成功,可以开始发送正式请求了。值得一提的事,Access-Control-Max-Age控制预检结果的浏览器缓存,若缓存还生效的话,是不用单独再发送OPTIONS请求的,匹配成功直接发送目标真实即可。

Access-Control-Max-Age使用细节

  1. 若浏览器禁用了缓存,也就是勾选了Disable cache,那么此属性无效。也就说每次都还得发送OPTIONS请求

  2. 判断此缓存结果的因素有两个:

    • 必须是同一URL(也就是Origin相同才会去找对应的缓存)

    • header变化了,也会重新去发OPTIONS请求(当然若去掉一些header编程简单请求了,就另当别论喽)

三、来源


表示请求来源的有三个字段,分别是 HostOrigin、**Referer**三个字段

Preview

3.1 Host

Host 表示请求地址,为客户端将要访问的远程主机。浏览器在发送Http请求时会带有此Header

格式: 域名+端口号

3.2 Origin

Origin 表示当前客户端页面地址。用于Cors请求和同域POST请求

格式: 协议+域名+端口

携带场景:

  • 同域请求只有post请求才会携带Origin
  • 跨域请求都会携带Origin,无论是get请求还是post请求

3.3 Referer

Referer 表示当前客户端页面地址。服务端一般使用Referer首部识别访问来源,可能会以此进行防盗链、统计分析、日志记录以及缓存优化等

格式: 协议+域名+端口号+路径+参数

携带场景: 除了以下不会携带的场景,其余场景都会携带

不会携带的场景:

  • 来源页面协议为File或者Data URI(如页面从本地打开的)
  • 来源页面是Https,而目标URL是http
  • 浏览器地址栏直接输入网址访问,或者通过浏览器的书签直接访问
  • 使用JS的location.href跳转

四、请求头


用于发起跨源请求的标头字段。请注意,这些标头字段无须手动设置。当开发者使用 XMLHttpRequest 对象发起跨源请求时,它们已经被设置就绪。

4.1 Origin

Origin 标头字段表明预检请求或实际跨源请求的源站。origin 参数的值为源站 URL。它不包含任何路径信息,只是服务器名称。注意,在所有访问控制请求中,Origin 标头字段总是被发送。

4.2 Access-Control-Request-Method

Access-Control-Request-Method 标头字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

4.3 Access-Control-Request-Headers

Access-Control-Request-Headers 标头字段用于预检请求。其作用是,将实际请求所携带的标头字段(通过 setRequestHeader() 等设置的)告诉服务器。这个浏览器端标头将由互补的服务器端标头 Access-Control-Allow-Headers 回答。

五、响应头


5.1 Context-Type

5.2 Access-Control-Allow-Origin

Access-Control-Allow-Origin 告诉浏览器允许该源访问资源。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符*,表示允许来自任意源的请求。

语法

Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin

5.3 Access-Control-Expose-Headers

在跨源访问时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应头,Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma,如果要访问其他头,则需要服务器设置本响应头。

Access-Control-Expose-Headers: <header-name>[, <header-name>]*

5.4 Access-Control-Max-Age

Access-Control-Max-Age 头指定了 preflight 请求的结果能够被缓存多久

5.5 Access-Control-Allow-Credentials

Access-Control-Allow-Credentials 头指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容。当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials

Access-Control-Allow-Credentials: true

5.6 Access-Control-Allow-Methods

Access-Control-Allow-Methods 标头字段指定了访问资源时允许使用的请求方法,用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

5.7 Access-Control-Allow-Headers

Access-Control-Allow-Headers 标头字段用于预检请求的响应。其指明了实际请求中允许携带的标头字段。这个标头是服务器端对浏览器端 Access-Control-Request-Headers 标头的响应。

五、问题

5.1 Request header field xfilesize is not allowed by Access-Control-Allow-Headers 问题

原因: 包含自定义Header字段的跨域请求,浏览器会先向服务器发送OPTIONS请求,探测该服务器是否允许自定义的跨域字段。如果允许,则继续实际的POST/GET正常请求,否则,返回标题所示错误。

解决:

  • Options 请求为:

    Access-Control-Request-Headers: content-type 
  • 那么,服务端必须作出应答:

    Access-Control-Allow-Headers: Content-Type

参考资料


说一下 CORS 的简单请求和复杂请求的区别