使用 CORS 解决跨域笔记

December 23, 2023

CORS 全称是 Cross-Origin Resource Sharing,翻译为 跨域资源共享,用于跨域场景发送 HTTP 请求。

两种请求

CORS 可以分为简单请求与非简单请求,非简单请求会多一个预检请求(Preflight request)。 有这个区分是为了兼容表单(form),因为 form 历史就可以发送跨域请求。

最新的 w3c 规范已经没有简单请求这个概念,不过原理一样。

同时满足三个条件就是简单请求,否则是非简单请求

  1. 请求方法是 HEAD、GET、POST 之一
  2. 请求头除了浏览器自带的之外,可以手动设置的只有以下几个:
  • accept
  • accept-language
  • content-language
  • content-type 有其他限制,看第三条
  • range
  1. content-type 的值为以下几种:
  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

简单请求

简单请求,会在请求头中多一个 origin 字段

Origin: http://example.com/

服务器响应中如果有对应的 Access-Control-Allow-Origin 字段,就允许跨域:

<!-- 准确匹配 -->
Access-Control-Allow-Origin: http://example.com/

<!-- 全部匹配 -->
Access-Control-Allow-Origin: *

这里建议的做法是准确匹配,一方面是更安全,另外后面携带 cookie 也会用到。

非简单请求(预检请求)

非简单请求会在正式的传输之前,增加一次预检请求,方法是 OPTIONS,并携带了三个和跨域请求有关的字段:

Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header, Content-Type

服务器收到请求后,根据这些字段判断是否允许,如果允许,会返回响应头:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-Custom-Header, Content-Type
Access-Control-Max-Age: 3600

Access-Control-Allow-Methods 表示服务器支持的所有跨域请求方法

Access-Control-Allow-Headers 表示服务器支持的所有头信息字段

Access-Control-Max-Age 表示本次预检请求的有效期,相当于缓存,单位是秒。再有效期内如果是 相同 URL 发出的跨域请求,不会再次发出新一条预检请求。如果是在生产环境使用 CORS,设置缓存很重要,可以减少请求数。Chromium 从 76 版本开始,缓存上限是 2 小时,即 7200s。

携带 cookie

CORS 默认不会携带 cookie,如果携带 cookie,Access-Control-Allow-Origin 需要精确匹配,并且需要额外设置:

请求设置

// XHR 设置
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://example.com/', true)
xhr.withCredentials = true
xhr.send(null)

// fetch 设置
fetch('https://example.com', {
    credentials: 'include',
})

响应设置:Access-Control-Allow-Origin 精确匹配,Access-Control-Allow-Credentials 设置为 true

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true

Profile picture

Written by xiaohai who lives and works in ShenZhen, building useful things.