古之立大事者,不惟有超世之才,亦必有坚忍不拔之志——苏轼
CORS 是什么
CORS 是一个 W3C 标准,全称是 Cross-origin resource sharing 译为 跨域资源共享,新增了一组 HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。
跨域资源共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求,从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证。
跨域资源共享机制的工作原理主要应用于三个场景:
- 简单请求
- 预检请求
- 认证请求
简单请求
简单请求是什么
请求同时满足所有下述条件,则该请求可视为 简单请求:
-
请求方法是以下三种方法之一:
- HEAD
- GET
- POST
-
HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
-
Content-Type
的值仅限于下列三者之一application/x-www-form-urlencoded
multipart/form-data
text/plain
值得注意的是,这些跨域请求与浏览器发出的其他跨域请求并无二致。如果服务器未返回正确的响应头部,则请求方不会收到任何数据。因此,那些不允许跨域请求的网站无需为这一新的 HTTP 访问控制特性担心。
基本步骤
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin
字段
下面是一个例子,浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加一个Origin
字段。
GET /resources/access-control-with-get/ HTTP/1.1
Host: aruner.net
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://arunranga.com/examples/access-control/simpleXSInvocation.html
Origin: http://arunranga.com
上面的头信息中,Origin
字段用来说明,本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。
下面是响应信息
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: http://arunranga.com
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
响应中携带了响应首部字段 Access-Control-Allow-Origin
, 使用 Origin
字段 和 Access-Control-Allow-Origin
字段就可以完成最简单的访问控制。
这个例子中 服务端返回的 Access-Control-Allow-Origin
为 http://arunranga.com
,表示该域的资源可以被外域访问,也已使用 *
,表示可以被任意外域访问。
预检请求
预检请求是什么
当浏览器发送一个非简单请求时,就会自动发送一个检测请求,所谓的检测请求就是在正式通信之前,增加一次 HTTP 查询请求,称为预检请求。
当请求满足下述任一条件时,即应首先发送预检请求:
- 请求方法是以下三种方法之一:
- PUT
- DEL
- CONNECT
- OPTIONS
- TRACE
- PATH
- HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
Content-Type
的值仅限于下列三者之一application/x-www-form-urlencoded
multipart/form-data
text/plain
预检请求要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。
预检请求可以避免跨域请求对服务器的用户数据产生未预期的影响。
请求消息
以下是 OPTIONS 预检请求消息示例:
OPTIONS /resources/access-control-with-post-preflight/ HTTP/1.1
Host: aruner.net
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-pingaruner
Origin: http://arunranga.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60
Sec-Fetch-Mode: cors
Referer: http://arunranga.com/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Access-Control-Request-Method
:表示该请求使用的是 POST方法Access-Control-Request-Headers
:表示该请求携带x-pingaruner
首部字段Origin
:请求来自哪个源
响应消息
以下是 OPTIONS 预检响应消息示例:
HTTP/1.1 200 OK
Date: Sat, 19 Dec 2020 15:20:30 GMT
Server: Apache
Access-Control-Allow-Origin: http://arunranga.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGARUNER, CONTENT-TYPE
Access-Control-Max-Age: 1728000
Upgrade: h2
Connection: Upgrade, Keep-Alive
Content-Length: 0
Cache-Control: max-age=172800
Expires: Mon, 21 Dec 2020 15:20:30 GMT
Vary: User-Agent
Keep-Alive: timeout=2, max=100
Content-Type: text/plain;charset=UTF-8
Access-Control-Allow-Methods
: 表示允许使用 POST, GET, OPTIONS 请求方法Access-Control-Allow-Headers
: 表示允许请求携带首部字段X-PINGARUNER
Access-Control-Max-Age
: 表示设置响应有效时间为 1728000 秒
认证请求
CORS 的一个有趣的特性是,可以基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨源 XMLHttpRequest
请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest
的某个特殊标志位。
如下代码所示
XMLHttpRequest.withCredentials = true;
将 XMLHttpRequest
的 withCredentials
标志设置为 true
,使得向服务器发送 Cookies。如果是简单请求,所以浏览器不会对其发起 预检请求 。但是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true
,浏览器将不会把响应内容返回给请求的发送者。
响应消息示例
HTTP/1.1 200 OK
Date: Sat, 19 Dec 2020 15:20:46 GMT
Server: Apache
Access-Control-Allow-Origin: http://arunranga.com
Cache-Control: no-cache
Pragma: no-cache
Access-Control-Allow-Credentials: true
Set-Cookie: pageAccess=1; expires=Mon, 18-Jan-2021 15:20:46 GMT; Max-Age=2592000
Upgrade: h2
Connection: Upgrade, Keep-Alive
Cache-Control: max-age=172800
Expires: Mon, 21 Dec 2020 15:20:46 GMT
Vary: Accept-Encoding,User-Agent
Content-Length: 80
Keep-Alive: timeout=2, max=100
Content-Type: text/plain;charset=UTF-8
只有当 Access-Control-Allow-Credentials
为 true 时才会将响应信息返回给开发者。
写在最后
通过 CORS 实现跨域访问是一个比较实用的一个方案,帮助我们解决了 Ajax 只能实用同源的限制。