HTTP访问控制(CORS)- 学习笔记

2018/04/28

今天在写一个简单的 mock-server 的时候遇到了跨域问题,导致前端页面不能正常与 mock-server 进行数据交互,之后我查询了相关资料,了解了一下 CORS 的相关知识。

一. 什么是浏览器同源策略

所谓同源是指:域名、协议、端口相同

同源策略又分为以下两种:

  • DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  • XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

二. 为什么要有跨域限制

1. 如果没有 DOM 同源策略:

  • 鲍勃做了一个假网站,里面只有一个宽高占满页面的 iframe,iframe 嵌套一个银行网站 http://mybank.com
  • 爱丽丝不小心进入假网站后发现进来除了域名,别的部分和银行的网站没有任何差别
  • 爱丽丝输入账号密码点击登录登陆后,鲍勃在主网站通过 JS 脚本跨域访问到 http://mybank.com 的 输入框 dom 节点,拿到了爱丽丝的密码。

2. 如果没有 XMLHttpRequest 同源策略:

  • 鲍勃做了一个网站 http://evil.com,网站外观可能是完全无害的,但实际嵌入了无感知的 恶意 AJAX 代码
  • 爱丽丝登录了自己的银行页面 http://mybank.com,银行 http://mybank.com 向爱丽丝的浏览器中添加用户标识 cookie
  • 爱丽丝鲍勃的网站,http://evil.com 向 http://mybank.com 发起 AJAX HTTP 请求,请求会默认把 http://mybank.com 对应 cookie 也同时发送过去
  • 银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据,鲍勃的网站将返回的数据发送给自己的服务器存储起来

三. CORS 简介

当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

而CORS 允许浏览器向跨源服务器,发出跨域请求,从而克服了AJAX只能同源使用的限制。

CORS是一个W3C标准,它同时需要浏览器和服务端的支持,其中浏览器端的支持如下

cors.png

浏览器基本都支持,因此,想要实现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

文章导航