文章目录
  1. 1. 一. 概述
  2. 2. 二. 两种请求
  3. 3. 三. 简单请求
    1. 3.1. 3.1 基本流程
    2. 3.2. 3.2 withCredentials 属性
  4. 4. 四. 非简单请求
    1. 4.1. 4.1 预检请求
    2. 4.2. 4.2 预检请求的回应
    3. 4.3. 4.3 浏览器的正常请求和回应

我只想重抄一遍!

这只是是笔记

一. 概述

CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。

整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

二. 两种请求

浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

  1. 请求方法是以下三种方法之一:
  • HEAD
  • GET
  • POST
  1. HTTP 的头信息不超出以下几种字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

三. 简单请求

3.1 基本流程

对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个 Origin 字段。

下面是一个例子,浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加一个 Origin 字段。

1
2
3
4
5
6
7
8
9
10
GET /api/user HTTP/1.1
Host: b.ds.com
Connection: keep-alive
Accept: application/json, text/plain, */*
Origin: http://a.ds.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36
Referer: http://a.ds.com/test
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
If-None-Match: W/"15-HbrNLXi6kTxO+I1w2Hii2zgOHYM"

上面的头信息中,Origin 字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段(详见下文),就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是 200。

如果 Origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

1
2
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://a.ds.com

和跨域有点的响应头属性以 Access-Control 开头,详细解释一下:

  • Access-Control-Allow-Origin
    该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个*,表示接受任意域名的请求。
  • Access-Control-Allow-Credentials
    该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器。这个值也只能设为 true,如果服务器不要浏览器发送 Cookie,删除该字段即可。
  • Access-Control-Expose-Headers
    该字段可选。CORS 请求时,XMLHttpRequest 对象的 getResponseHeader()方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回 FooBar 字段的值

3.2 withCredentials 属性

上面说到,CORS 请求默认不发送 Cookie 和 HTTP 认证信息。如果要把 Cookie 发到服务器,一方面要服务器同意,指定 Access-Control-Allow-Credentials 字段。

1
Access-Control-Allow-Credentials: true

另一方面,开发者必须在 AJAX 请求中打开 withCredentials 属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否则,即使服务器同意发送 Cookie,浏览器也不会发送。或者,服务器要求设置 Cookie,浏览器也不会处理。

但是,如果省略 withCredentials 设置,有的浏览器还是会一起发送 Cookie。这时,可以显式关闭 withCredentials。

需要注意的是,如果要发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie。

四. 非简单请求

4.1 预检请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT 或 DELETE,或者 Content-Type 字段的类型是 application/json。

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求(OPTIONS),称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错。

下面是一段浏览器的 JavaScript 脚本。

1
2
3
4
5
6
axios({
url: 'http://b.ds.com/api/user',
headers: {'Add-Custom-Header': 'xxxxxx'
}).then(res => {
console.log(res);
});

这里发送了发送一个自定义头信息 Add-Custom-Header。
浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求

1
2
3
4
5
Request URL: http://b.ds.com/api/user
Request Method: OPTIONS
Status Code: 204 No Content
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

预检”请求用的请求方法是 OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是 Origin,表示请求来自哪个源。

除了 Origin 字段,”预检”请求的头信息包括两个特殊字段。

  1. Access-Control-Request-Method
    该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法。
  2. Access-Control-Request-Headers
    该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段,上例是 Add-Custom-Header。
    扩展一个
  • Access-Control-Max-Age
    该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是 20 天(1728000 秒),即允许缓存该条回应 1728000 秒(即 20 天),在此期间,不用发出另一条预检请求。

4.2 预检请求的回应

服务器收到”预检”请求以后,检查了 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers 字段以后,确认允许跨源请求,就可以做出回应。

1
2
3
4
5
6
7
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: xxxxxx
Access-Control-Allow-Methods: GET, HEAD, PUT, PATCH, POST, DELETE
Access-Control-Allow-Origin: http://a.ds.com
Connection: keep-alive
Date: Wed, 06 Nov 2019 10:11:39 GMT
X-Powered-By: Express

上面的 HTTP 回应中,关键的是 Access-Control-Allow-Origin 字段,表示http://a.ds.com可以请求数据。

如果浏览器否定了”预检”请求,会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被 XMLHttpRequest 对象的 onerror 回调函数捕获。控制台会打印出如下的报错信息。
Access to XMLHttpRequest at ‘http://b.ds.com/api/user' from origin ‘http://a.ds.com' 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.

4.3 浏览器的正常请求和回应

一旦服务器通过了”预检”请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。

原文出处

文章目录
  1. 1. 一. 概述
  2. 2. 二. 两种请求
  3. 3. 三. 简单请求
    1. 3.1. 3.1 基本流程
    2. 3.2. 3.2 withCredentials 属性
  4. 4. 四. 非简单请求
    1. 4.1. 4.1 预检请求
    2. 4.2. 4.2 预检请求的回应
    3. 4.3. 4.3 浏览器的正常请求和回应
顶部