CORS(cross-origin resource sharing,跨域资源共享)是目前针对Ajax跨域的最好方法,原理很简单,在后端返回的响应头中设定白名单(如 Access-Control-Allow-Origin ),符合条件的即可得到资源。对于CORS的实现,浏览器端是自动执行相关操作,无需用户进行额外的处理,所以实现的关键在于后端。

两类请求

浏览器将CORS请求只要分为两个大类:简单请求和复杂请求,浏览器会对这两类请求分别进行不同的操作。

符合以下两大条件的即为简单请求

  1. 请求方法是三者之一
    1. GET
    2. HEAD
    3. POST
  2. HTTP头部信息不超过这几种字段

    • Accept
    • Accept-Language
    • Content-Language
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
    • Content-Type :限于这三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

      简单请求

      如果浏览器发现发送的是简单请求,那么会自动在请求头中添加 Origin 字段,指明请求源。譬如说, http://localhost:8080 向URL http://localhost:3000/api 发出跨域Ajax请求,请求头会如下所示:
      2020-05-31 10-47-03 的屏幕截图.png
      服务器接收到请求会正常返回,浏览器会检验相关的响应头如 Access-Allow-Control-Origin ,如果不符合就会抛出错误,被 XMLHttpRequestonerror 捕获。注意在这个过程当中,如果服务端仅仅设定了CORS相关的响应头,而没有人工针对origin做出任意额外的操作,相关接口的数据还是会发送回来,只不过会被浏览器拦截,导致Ajax无法获取到相关的数据。

      复杂请求

      不符合之前所说的两大条件的即为复杂请求,如请求类型为 PUTDELETE 等,或者带有自定义的请求头如 X-Custom-Request: hello
      对于此类请求,浏览器在发出正式请求前会先发出一个预检请求preflight ),通过此请求来向服务器确认此域名、请求方法、请求头等信息在不在许可范围内,得到了肯定答复以后才会发出 XMLHttpRequest 请求。
      以下 http://localhost:8080 由axios发出的请求为例:
      1. instance = axios.create({
      2. baseURL: "http://localhost:3000",
      3. headers: { "X-Custom-Header": "Rust" },
      4. });
      5. instance.get("/api").then((response) => {
      6. console.log(response);
      7. });
      通过抓包可以看到浏览器一共向服务器发出两个请求:
      2020-05-31 11-34-26 的屏幕截图.png
      从上图看出,第一个请求为预检请求,请求方法为 OPTIONS 。浏览器在请求时会带上 Access-Control-Request-HeadersAccess-Control-Request-Method 分别指定请求的头部和方法,响应返回后,浏览器根据CORS相关响应头来检查Ajax的请求信息,完全符合才会发出正式请求,如果有一项不符合条件,则会抛出错误。此外,如果服务器响应头不包含任何CORS相关响应头,浏览器也会认为服务器拒绝请求,从而抛出错误。
      此外,并不是每一次的复杂请求都会进行预检,服务端可以设定 Access-Control-Max-Age 指定浏览器预检请求的时间,在这段时间内进行复杂请求可以不进行预检。

      Cookie相关处理

      默认情况下进行跨域请求浏览器端是不会携带cookie,服务端也不会设置浏览器的cookie。如果浏览器要发送cookie,则要同时满足两个条件:
  3. 浏览器的xhr请求设定 withCredentials 属性为true,说明同意服务器设定cookie和发送cookie

  4. 服务器则要设定 Access-Control-Allow-Credentials 响应头为true,说明同意接受cookie

如果服务端想设定cookie,则只需浏览器设定 withCredentials 为true即可,自身响应头可以不进行设定

此外,要携带cookie, Access-Control-Allow-Origin 就不能设定为 * ,否则报错

CORS响应头总结

  • Access-Control-Allow-Origin:<origin> | * 允许访问的url
  • Access-Control-Expose-Headers :默认情况下xhr只能获取 Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma 响应头,如果要访问更多,则需要服务器进行设定
  • Access-Control-Max-Age :单位为秒,设定一定时间范围内不用再进行预检请求
  • Access-Control-Allow-Credentials :是否允许浏览器带上cookie等身份信息
  • Access-Control-Allow-Methods :允许的请求方法
  • Access-Control-Allow-Headers :在预检请求应用,指定允许带上的请求头

    CORS请求头总结

  • Origin :指定源站的域名

  • Access-Control-Request-Method :向服务器说明请求的方法
  • Access-Control-Request-Headers:向服务器说明请求的头部

    总结

  • 浏览器将CORS请求分为两类,简单请求和复杂请求。

  • 对于简单请求,浏览器正常发出请求,但是会根据服务器返回的CORS相关响应头来判断是否接收数据
  • 对于复杂请求,浏览器会先发出预检请求,此请求为OPTIONS请求,通过响应头判断服务器是否允许相关域名、方法和请求头。预检通过则会接着发出正常请求,不通过则直接抛出错误。
  • 浏览器要携带cookie,需要请求时设定 withCredentials 属性为true,服务端也要通过设定 Access-Control-Allow-Credentials 为true来表示允许接收
  • CORS功能强大,所有请求方法都能支持,能允许特定网站来请求数据,能自行设定是否携带cookie