本人博客文章地址:点击进入
今天在写一个简单的 mock-server 的时候遇到了跨域问题,导致前端页面不能正常与 mock-server 进行数据交互,之后我查询了相关资料,了解了一下 CORS 的相关知识。
一. 简介
当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
而CORS 允许浏览器向跨源服务器,发出跨域请求,从而克服了AJAX只能同源使用的限制。
CORS是一个W3C标准,它同时需要浏览器和服务端的支持,其中浏览器端的支持如下
浏览器基本都支持,因此,想要实现CORS通信,只要服务器实现了CORS接口即可。
二. 简单请求与非简单请求
cors请求根据发出的请求是否满足一些条件,将请求分为两种:简单请求与非简单请求。
1. 简单请求
1.1 基本
若请求满足所有下述条件,则该请求可视为“简单请求”:
- 请求方法为 Head、Get、Post 中的一个
- 请求头信息中仅包含 对 CORS 安全的首部字段集合 :Accept、Accept-Language、Content-Language、Content-Type,且 Content-Type 仅限于application/x-www-form-urlencoded、multipart/form-data、 text/plain 中的一个。
对于简单请求,浏览器直接发出一个CORS请求,并在请求头中加入一个 Origin 段来描述本次请求来自哪个源(协议 + 域名 + 端口)。如下就是一个简单请求的请求头:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
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
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example
而服务器则需要在返回头中包含 Access-Control-Allow-Origin 段,来表明哪些外域可以访问。如下是一个返回头
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
其中 Access-Control-Allow-Origin:* 表明该资源可以被任意外域访问。如果服务端仅允许来自 http://foo.example 的访问,该首部字段的内容如下:Access-Control-Allow-Origin: http://foo.example。现在,除了 http://foo.example,其它外域均不能访问该资源。
1.2 withCredentials
CORS请求默认不发送Cookie和HTTP认证信息,如果要发送Cookie,首先需要在 AJAX 请求中打开 withCredentials 属性,且服务器的返回头中必须包含 Access-Control-Allow-Credentials:true 。需要注意的是,当请求中包含cookie时,即打开了 withCredentials 后,服务器返回头中 Access-Control-Allow-Origin 就不能再设置为 *,而必须要显示指明允许哪些源访问。
2. 非简单请求
2.1 基本
非简单请求是指那些包含特殊要求的请求,对于这一类请求,浏览器会在正式请求之前发出一个OPTION请求来询问服务器是否支持该次请求,这个OPTION请求被称为‘预检请求’。在服务端回应允许后,浏览器才会发出真正的非简单请求。
2.2 预检请求
如下是一个 预检请求 的请求头:
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
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
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
除了Origin 字段,还包含了Access-Control-Request-Method 和 Access-Control-Request-Headers 字段,分别表明 CORS 请求会用到的 请求方法 和 额外的请求头字段。
服务端在收到预检请求后会做出回应,如下是预检请求的返回头:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Access-Control-Allow-Methods 表示服务端允许的请求方法,Access-Control-Allow-Headers 表示服务端允许的请求头额外字段,Access-Control-Max-Age 指定本次预检请求的有效期,有效期内不用发出另一条预检请求。
2.2 实际请求
预检请求完成后,浏览器会将实际请求发出,和简单请求一致。
三. 相关请求、响应头字段
1. 请求
Origin:预检请求或实际请求的源站。协议 + 域名 + 端口。不管是否为跨域请求,ORIGIN 字段总是会被发送。
Access-Control-Request-Method:预检请求专用。将实际请求所使用的 HTTP 方法告诉服务器。
Access-Control-Request-Headers:预检请求专用。将实际请求所携带的额外头字段告诉服务器。
2. 响应
Access-Control-Allow-Origin:指定允许访问该资源的外域 URI。当请求打开 withCredentials 属性时,不可返回 ‘*’。
Access-Control-Expose-Headers:允许浏览器通过 getResponseHeader 访问的额外字段。
Access-Control-Max-Age:预检请求缓存时间。
Access-Control-Allow-Credentials:当请求打开 withCredentials 属性时,需要返回true。
Access-Control-Allow-Methods:预检请求专用。指明实际请求所允许使用的 HTTP 方法。
Access-Control-Allow-Headers:预检请求专用。指明实际请求所允许携带的额外头字段。
四. 常见跨域错误
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'XXX' is therefore not allowed access.
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'XXXX' is therefore not allowed access.
以上两种错误都是由于服务端返回头没包含Access-Control-Allow-Origin字段导致的,解决办法是添加上该字段并让请求源包含在字段中。
Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains the invalid value 'XXX'. Origin 'XXX' is therefore not allowed access.
The 'Access-Control-Allow-Origin' header contains the invalid value 'XXX'. Origin 'XXX' is therefore not allowed access.
以上两种错误都是由于服务端返回头中的Access-Control-Allow-Origin字段不包含请求源,解决办法是修改该字段使请求源包含在字段中。
Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'XXX' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'XXX' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
以上两种错误都是由于请求打开 withCredentials 属性,但服务端将Access-Control-Allow-Origin设置为了‘’,解决办法是用精确写法替换‘’。
Request header field XXX is not allowed by Access-Control-Allow-Headers in preflight response.
这种错误是由于请求头中包含Access-Control-Request-Headers,但服务器未返回对应的Access-Control-Allow-Headers
作者博客地址:https://liuhuihao.com
作者gitHub:https://github.com/geminate