利用CORS机制来实现跨域

CORS — 本文适用于现代的支持cors的浏览器,比如chrome

##【理解同源】
我一个域名下的网页,只要请求跨域的资源,都叫跨域。例如:

  • 域名不同,a.com与 b.a.com也不同。
  • 端口不同
  • 协议不同:http和https

在不同源的情况下,嵌入不同源的资源浏览器是允许的: 例如html里使用嵌入的图片,视频,js,css。

而浏览器不允许的情况是:

  • ajax发起跨域请求,因为这样浏览器任务可能有潜在的危险。
  • Web 字体 (CSS 中通过 @font-face 使用跨站字体资源)。( 由于跨源字体是不可以的,因此,我们就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用,这有利于我们保护版权。)
  • WebGL 贴图
  • 使用 drawImage API 在 canvas 上画图

其实上面说不允许也不严谨,其实有的时候你的ajax跨域请求,浏览器是向服务器发起了请求的,只是他把响应结果给在浏览器客户端里面阻挡住了,不让你的js使用。(这个下文看完就明白了)

我们最关注的,其实就是ajax跨域,因为这块需求强烈。

情景1:【直接发起的get/post等请求,没有options预请求】

我们一般认为,发起跨域请求就是ajax请求的地址是不同源的一个地址。 至于什么是同源,上面已经讲了。
那么,跨域的情况就只有这么简单的一种情况吗。不是的,有些请求

我ajax请求一个跨域的url,浏览器就会向他发起get或post之类的请求。没有options这种预先请求。
哪些情况下,会直接发起请求而不option呢:

  • 数据类型(Content-Type)是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain。 只要你的ContentType被改成别的比如application/json,就算是个很简单的get请求,那么也会提前发个options请求。
  • 不使用自定义请求头(类似于 X-Modified 这种

当你的ajax配置为只有上面这种情况的时候,浏览器才会直接按照你ajax的意图发起请求,比如你向一个url发起get请求,且请求头里的content-type是application/x-www-form-urlencoded。而且你没有在http头里添加任何自定义的东西。
这个时候,服务器会收到你ajax 的跨域请求,不管是get还是post。
服务器也会根据他的逻辑做出响应。
但是,浏览器收到服务器的响应后,发现服务器的http头里不允许跨域,那么浏览器就会把这个响应结果给隐藏,不让你的ajax拿到可用的结果。于是,从表面上看,浏览器不允许我们ajax发起跨域请求。
怎样让这种情况下,ajax可用呢? 答:只需要服务器这边允许这个域名跨域访问即可,这样做:
Access-Control-Allow-Origin: *
或者把星号换成一个实际的域名http://a.com:80

安全提醒: 虽然服务器可以拿到这个请求,但是cookie在跨域时是没有带上的。但黑客有个方法带上,那就是document.cookie放到url里带上。
前端开发时要在本地调用不是本地localhost同一个地址或端口的API怎么办呢, 可以在本地前端server里进行路由转发,这样前端请求自己的域名就能让本地server去用后台技术请求API了。 后台技术请求API不会受到CORS同源策略的限制,而且这种方式可以解决跨域cookie的问题,因为本地server会收到同域下ajax 的cookie,他就可以以这个cookie去请求后端另一个域名的API。

场景2【带options预请求的情况】

一旦你ajax请求的代码里面,通过xhr.setHeaders修改了http请求头,
比如让你的Content-Type不是这三种: application/x-www-form-urlencoded, multipart/form-data 或 text/plain。
不是这三种,其他的有哪些呢? 比如 application/json application/xml
比如,你添加了自定义请求头。
这种情况下,浏览器的ajax请求并不会直接发起你意图的请求,浏览器会偷偷先发起个options请求,http头里面会包含了一些询问字段,例如:
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:GET
然后,服务器这边,你得在options处理器里面,返回这样的http头:
Access-Control-Allow-Origin: http://foo.example 或者 星号
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: 客户端想使用的头,比如Content-Type, X-Modifyed等。

一旦浏览器通过options发现服务器不支持你的请求源域名, 或者服务器不支持你的请求method类型,或者服务器不支持你http请求里所要求的http头。那么浏览器就不会让你的ajax再发起真实的get或post请求。

如果你服务器的options处理器返回了那些Allow信息,那么浏览器就会发起真实的ajax请求,然后服务器的get或者post处理器,还得继续在响应里面把Access-Control-Allow-Origin, Access-Control-Allow-Headers这些东西加上。 要不然,在浏览器接收的这一关又过不了!因为浏览器虽然通过了options那一关,于是浏览器愉快的向服务器发起post请求,然后post响应里面发现没有允许跨域的那些字段,那么浏览器就像上面讲到的那些情况似的,把响应内容在客户端阻断,不让你的ajax程序使用。

所以,浏览器这种先option再get提交的CORS机制可以防止黑客恶意将浏览器的东西提交给服务器。

场景3【带cookies等认证信息的跨域请求情况】

这种情况,附属于上面两种情况。 也就是说,带options的这种情况可以发送cookie; 不带options的这种情况也可以发送cookie。
想让ajax带上cookie很简单,只需要学习一个http头:
Access-Control-Allow-Credentials: true
只要带上cookie,且能通过上面讲到的那些情况,那么浏览器就给服务器发送request请求,那么服务器就能拿到客户端的请求信息(包括cookie)。注意:options预请求的时候不会带上cookie。

jquery里面可以用$.ajax()里面设置xhrFields: {
withCredentials: true
}
来让CORS传递cookie。

node里面服务端如果支持跨域,可以用cors这个包。

但是,就算浏览器满足了所有的跨域请求条件,请求发出了,然后服务器虽然能拿到cookie,也能做响应。但是浏览器拿到响应照样会阻止。 为什么呢? 因为浏览器发现服务器响应http头里面没有Access-Control-Allow-Credentials: true
没有这个字段,就说明服务器不想传送凭证。于是浏览器又在本地阻止了ajax 拿到响应数据。

客户端get或者post的时候,可以设置xhr.withCredentials = true;
你服务器要给返回Access-Control-Allow-Credentials: true,那么浏览器就会把响应结果让js继续使用。否则浏览器会把响应阻断,不让js用。

当带options这种情况的时候,你要在服务器的options处理器里面,放上’Access-Control-Allow-Credentials’: true. 这样的话,客户端ajax预请求的时候才知道你服务器允许传cookie,然后浏览器会发起真正的get或者post,否则真正的传送cookie请求是发布出去的,服务器根本接收不到。
当你options请求能够通过, 此时,你同时需要在服务器的get和post处理器里面设置上’Access-Control-Allow-Credentials’: true, 要不然真正的请求又发现服务器不允许传cookie,这样浏览器拿到响应就自动阻止js使用这个响应。

注意: 带cookie这种情况,Access-Control-Allow-Origin必须设置为url,不能用通配符。为什么呢? 我感觉是为了防止csrf,比如我给你发个色情网站链接让你打开,我在里面偷偷去用js 向qq空间的添加文章发送跨域post请求,其实在你浏览器里发起带cookie认证的请求,会把你的qq的cookie带上去,也就借用你的身份给你空间发文章了。 而qq空间如果服务器端设置Access-Control-Allow-Origin: ,就很危险,因为我的色情网站都能够跨域带cookie去请求。 所以,为了避过那些二逼的程序员,w3c直接严格规定了这个约定,那么qq空间的服务器端就不可能设置为 ,他只可能设置为一个自己家可信赖的域名,供自己方便使用。

所以,CORS就是服务器和浏览器之间的一系列配合。我们要弄懂如何跨域,就要明白什么时候服务器该加什么头。