西岭老湿
首发于西岭老湿
所有人都应该知道的跨域及CORS

所有人都应该知道的跨域及CORS

说起跨域,很多web程序员并不陌生,出于对安全问题的考虑,1995年由Netscape提出同源策略,浏览器在发送Ajax请求时,只接收同域服务器响应的数据资源;那么什么才算同域呢?很简单,协议、域名、端口全部相同才算同一域下,三个条件有一个不一致,都不算同域,既跨域;

因此,即使是我们自己的域名服务器,而二级域名或三级域名不一致,也会出现跨域,如:img.xiling.meblog.xiling.me 之间需要数据交互,就跨域了;

这就让人很苦恼了,明明都是我自己的域名,我自己的服务器,还是受到了同源策略的保护无法进行数据交互;这不能算是设计缺陷,只能说当年考虑不周全,也有可能因为当年网络通信协议的不健全,原因不得而知;但宝宝心里苦啊,可宝宝那么聪明,宝宝还是有办法滴啊;


于是,各路大神献出奇招来解决,不管白猫花猫,抓住老鼠就是好猫,只要能够使两个不同域下的数据进行顺利交互就可以了;各路解决方案中,最为典型的有两个:

JSONP和同域代理;

同域代理就是使用Ajax向同域下的后台发送请求,同时携带真实请求的地址及参数,后台接受请求后直接根据地址及参数转发请求,因为后台是可以直接模拟HTTP客户端发送请求的,所以没有跨域问题,而后台接受到响应数据后再原样返回给前端浏览器,从而实现跨域数据交互;


JSONP是利用了 script 标签的 src 属性来实现跨域数据交互的,因为浏览器解析HTML代码时,原生具有src属性的标签,浏览器都赋予其HTTP请求的能力,而且不受跨域限制,使用src发送HTTP请求,服务器直接返回一段JS代码的函数调用,将服务器数据放在函数实参中,前端提前写好响应的函数准备回调,接收数据,实现跨域数据交互;

JSONP 是目前应用最为广泛的技术解决方案,我们使用的 百度、淘宝、360搜索等各大搜索引擎的关键字推举都在使用JSONP技术;

JSONP和同域代理,本质上并没有解决Ajax跨域的问题,只是绕开这个问题而另辟蹊径实现的跨域数据交互,在数据交互层面上可以看做技术不成熟时的临时解决方案;但是JSONP 和同域代理 使用了很多年,当然跨域问题也存在了很多年,终于有人看不下去了,提出了浏览器与服务器跨域通信的安全性通信策略,它就是我们今天的主角 跨域资源共享(Cross-origin resource sharing),简称 CORS ; 有了它,大家可以安全放心的抛弃 JSONP 和 同域代理了,步入正统的跨域数据交互的殿堂;


CORS 的使用得益于 HTTP-1.1 协议版本的推广的大面积使用,它由一系列传输的HTTP头组成,这些HTTP头有两个作用,

1:用于阻止还是允许浏览器向其他域名发起请求;

2:用于接受还是拒绝其他域名返回的响应数据;


也就意味着,我们只要搞清楚什么样的头信息是控制浏览器发送还是不发送请求,什么样的头信息控制浏览器接受还是拒绝服务器的响应数据,这两点搞明白,CORS就算彻底搞清楚了;


我们先从一个普通的Ajax跨域说起,可能还有人不知道这个事情,就是Ajax在发送跨域请求时,请求是能够正常发送出去的,同时服务器也是可以正常接收并能够做出响应的:


再次强调,跨域是浏览器正确做出请求,服务器也能正确做出响应,是浏览器拒绝接收跨域服务器返回的数据;

而在上面的请求头中,我们能够发现与普通的HTTP请求不一样的是,添加了Origin请求头,它指示了请求来自于哪个站点,这个请求头是浏览器在发现本次为跨域请求时自动添加的,目的就是告知服务器本次请求是那个域发起的,而此时,如果服务器允许本次请求响应的数据是可以共享的,那么服务器需要添加一个 Access-Control-Allow-Origin 响应头,并指明可以共享数据的域,如下:


而Access-Control-Allow-Origin 响应头的值,可以设置为“*” (星号),表示可以与任意域进行数据共享;

很多人也认为使用CORS解决跨域很简单,只需要在服务器添加响应头 “ Access-Control-Allow-Origin :* ” 就可以了,

其实不然,因为在CORS中,所有的跨域请求被分为了两种类型,一种是简单请求,一种是复杂请求 (严格来说应该叫‘需预检请求’);简单请求与普通的ajax请求无异;但复杂请求,必须在正式发送请求前先发送一个OPTIONS方法的请求已得到服务器的同意,若没有得到服务器的同意,浏览器不会发送正式请求;

上面的请求其实就是个简单请求类型;那么简单类型和复杂类型是如何区分的呢?

满足以下所有条件,被视为简单类型的请求:

1:请求方法必须是 GET、HEAD、POST中的一种,其他方法不行;

2:请求头类型只能是 Accept、Accept-Language、Content-Language、Content-Type,添加其他额外请求头不行;

3:请求头 Content-Type 如果有,值只能是 text/plain、multipart/form-data、application/x-www-form-urlencoded 中的一种,其他值不行;

4:请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;

5:请求中没有使用 ReadableStream 对象。

而以上的条件有任意一条不满足,则视为复杂类型的请求;前面说过,如果是复杂请求,浏览器会先发送OPTIONS方法的请求以取得服务器的确认,如下,我随意的添加了一个请求头:


我们可以清楚的看到,浏览器做出了跨域拦截的处理,这是因为我们添加了额外的请求头信息,而在发送OPTIONS请求后,并没有得到服务器的允许,所以,浏览器根本就没有发送正式请求,注意,得不到服务器确认,浏览器压根就不会发送正式请求,要解决这个问题,我们就需要在服务器添加对应的响应头信息:


至此,CORS基本的原理就介绍完了;

总结一下:

出于安全性的考虑,浏览器引入同源策略;

可以使用JSONP及同域代理绕过同源策略进行跨域数据共享,但目前不再建议使用;

CORS本质上就是使用各种头信息来是浏览器与服务器之间进行身份认证实现跨域数据共享;

CORS中最常使用的响应头为 Access-Control-Allow-Origin、Access-Control-Allow-Headers、Access-Control-Expose-Headers;

CORS中最常使用的请求头为 Origin、Access-Control-Request-Headers、Access-Control-Request-Method;

CORS请求分为简单请求和复杂请求(需预检请求);


代码示例:



下面列出CORS头信息共有哪些及对应的含义:

Access-Control-Allow-Origin : 指示请求的资源能共享给哪些域,可以是具体的域名或者*表示所有域。

Access-Control-Allow-Credentials : 指示当请求的凭证标记为 true 时,是否响应该请求。

Access-Control-Allow-Headers : 用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。

Access-Control-Allow-Methods: 指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。

Access-Control-Expose-Headers : 指示哪些 HTTP 头的名称能在响应中列出。

Access-Control-Max-Age : 指示预请求的结果能被缓存多久。

Access-Control-Request-Headers :用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。

Access-Control-Request-Method : 用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。

Origin : 指示获取资源的请求是从什么域发起的




关于作者: note.youdao.com/share/?

欢迎关注 西岭老湿 微信公众号

欢迎关注西岭老湿知乎专栏: zhuanlan.zhihu.com/xili

西岭老湿 博客地址:http://blog.xiling.me

发布于 2019-01-04

文章被以下专栏收录