CORS

跨域资源共享,是基于 HTTP 上一个用来解决跨域的方法,需要服务端和浏览器端同时支持

CORS 分为简单请求和非简单请求
简单请求:简单请求只要满足以下两大条件就是简单请求

  1. 请求方法只能是 GET、POST、HEAD
  2. 请求头中不超出以下几个字段
    accept,
    accept-language,
    context-type, 其中 content-type 只能是 application/x-www-form-urlencoded、text/plain、multipart/form-data,
    Last-Event-ID

否则就视为非简单请求,浏览器对于两种请求处理是不一样的

简单请求

流程

对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个 origin 字段,用来标识是哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求

1
2
3
4
5
GET/cors HTTP/1.1
Origin: http://baidu.com
Host: baidu.com
Accept-Language: en-US
Connection: keep-alive

上面头信息中,origin字段用来说明,本次请求来自哪个源。服务器根据这个值决定是否同意这次请求

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

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

1
2
3
4
Access-Control-Allow-Origin: http:baidu.com // 该字段是必须的,它的值要么是请求时的 Origin 字段,要么是 *
Access-Control-Allow-Credential: true // 可选值,表示是否允许发送 Cookie, 默认情况下,Cookie 不包括在 CORS 请求之中,设为 true,cookie 可以包含在请求中,也可以设为 true 如果服务器不要浏览器发送 cookie 就删除该字段。
Access-Control-Expose-Headers: FooBar // 可选,cors 请求时,XMLHttpRequest 的只能拿到响应头中 6个基本字段(cache-control、content-language、content-type、expires、last-modified、Pragma),如果想拿其他的,就必须在 Access-Control-Expose-Headers 中指定。
Content-Type: text/plain

非简单请求

非简单请求是那种对服务器有特殊要求的,比如请求方法是 PUT、DELETE,或者 Content-type 是 application/json

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

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

预检请求 是通过 OPTIONS 发的,表示这个请求是个询问请求

1
2
3
4
5
6
7
8
OPTIONS /cors HTTP/1.1
Origin: http:baidu.com
Access-Control-Request-Method: PUT //表示CORS 会用到哪些请求方法
Access-Control-Request-Headers: X-Custom-Header // 额外发送的头部信息
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

回应预检请求

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

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 ok
Date: Mon, 01 Dec 2024 01:15:39 GMT
Server: Apache/2.0.61(Unix)
Access-Control-Allow-Origin: http://baidu.com // 表示允许 baidu.com 的跨域请求
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html
Content-Encoding: gzip
Content-Length: 0
Connection: Keep-Alive
Content-Type: text/plain

如果服务器否定了“预检”请求,会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头部信息,那么浏览器就会认为 服务器不同意预检请求

正常响应 CORS 相关字段如下:

1
2
3
4
Access-Control-Allow-Methods: GET,POST,PUT // 返回服务器支持的所有请求方法
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

通过预检请求

通过预检请求后,浏览器每次发起都会带有 Origin 字段,服务器响应 也会返回相应的 Access-Control-Allow-Origin