brunoyang / blog

134 stars 13 forks source link

cors和koa-cors #11

Open brunoyang opened 8 years ago

brunoyang commented 8 years ago

近期在工作中用到了cors,所以写下这篇分享,本文基于koa-cors 1.0.1

啥是cors呢

Cross-Origin Resource Sharing(cors),顾名思义,跨域资源共享,也就是一种实现跨域的手段,想要的解决的问题是跟使用jsonp一样的。要想知道跨域是为了什么,得先知道什么是跨域。跨域(cross-orgin)是因为有同源策略(same-origin policy)的存在。浏览器为了保证加载的脚本等资源都是可控安全的,就加了一个强制性的规定,非同域名下的资源不得加载。同域名是指两个域名之间,端口,协议,以及子域都应相同

这是与http://www.alipay.com的通信结果

域名 结果 原因
http://www.alipay.com/dir/a.js 成功 同一域名
http://news.alipay.com/a.js 失败 非同一子域
https://www.alipay.com/a.js 失败 协议不同
http://www.alipay.com:1234/a.js 失败 端口不同

在chrome的network面板里可以看到,发给非同域的请求其实是发出去了的,只是浏览器在拿到对方服务器返回的时候数据时,看到这个响应并不是从当前域名返回的,就302了这个响应,同时报了个错。这样看来,跨域都是浏览器的锅,jsonp、iframe等手段都是为了绕过这一障碍才发明的。浏览器也不愿天天背这个锅,码农何苦为难码农,于是出现了cors这一手段,让浏览器天生支持跨域。

cors的实现非常简单,前端甚至都不需要做修改,只需要在对方服务端的响应头里加个字段Access-Control-Allow-Origin就可以了。Access-Control-Allow-Origin的值可以是*,也就是代表任何域名都可以来拿我的资源,也可以指定域名,只有指定的域名可以拉取资源。

cors还有些特性也需要详说的:

预请求(Preflighted)

预校验是指某些特殊情况下,需要在发送真正的ajax请求之前发送一个options方法的请求,来『探测』一下我们的请求服务端接不接受,当然这一步是浏览器自动的,无需手动。『特殊情况』就是指下面的两种情况:

在预请求的请求头里会额外再带上两个字段,分别是Access-Control-Request-Method(必带),Access-Control-Request-Headers(如果设置了自定义请求头),Access-Control-Request-Method的值就是该ajax请求所用的方法。

带上cookie的ajax跨域请求(credentials)

可能有的同学会说,这根本不给力啊,如果我们做的是安全性比较高的服务,cors过来的请求根本没法做校验,很可能会出安全事故啊~ w3c的同学都不是吃素的,早就为你考虑到啦!一般来说,跨域的ajax请求是不带cookie的,而cors的ajax请求就不一样了,人家可以带cookie去服务端,这样就可以让服务端来判断这个请求是否合法。而且让ajax带cookie很简单,一句话的事儿:xhr.withCredentials = true;。若服务端认为这个请求是合法的,返回的响应头里必须带上Access-Control-Allow-Credentials: true,若为其他值或不带这个字段,浏览器会把withCredentials发出的请求的响应通通拒绝掉。

这项技术的浏览器兼容情况还是不错的,IE8 ~ IE9使用XDomainRequest,其他浏览器都使用XMLHttpRequest原生支持。

koa-cors

说完了cors和cors的原理,我们接着来讲koa-cors。

使用

var koa = require('koa');
var cors = require('kcors');

var app = koa();
app.use(cors());

cors方法可以传入一个对象options

{
    origin:允许发来请求的域名,对应响应的`Access-Control-Allow-Origin`,
    allowMethods:允许的方法,默认'GET,HEAD,PUT,POST,DELETE',对应`Access-Control-Allow-Methods`,
    exposeHeaders: 允许客户端从响应头里读取的字段,对应`Access-Control-Expose-Headers`,
    allowHeaders:这个字段只会在预请求的时候才会返回给客户端,标示了哪些请求头是可以带过来的,对应`Access-Control-Allow-Headers`,
    maxAge:也是在预请求的时候才会返回,标明了这个预请求的响应所返回信息的最长有效期,对应`Access-Control-Max-Age`
    credentials:标示该响应是合法的,对应`Access-Control-Allow-Credentials`
}
var requestOrigin = this.get('Origin');
if (!requestOrigin) {
  return yield* next;
}

如请求头不带Origin字段,说明根本不是cors请求,直接忽略。

if (this.method !== 'OPTIONS') {
  ...
} else {
  ...
  if (!this.get('Access-Control-Request-Method')) {
    // this not preflight request, ignore it
    return yield* next;
  }
  ...
}

若该请求不是OPTIONS方法,说明不是预请求,则根据options中的字段设置响应头,若该方法是OPTIONS方法,但不带Access-Control-Request-Method,说明不是预请求,仍然直接忽略。

this.status = 204;

在设置完需要设置的字段后,由于不需要返回具体内容,到这里就可以直接返回个204了。204表示不带响应体的成功响应。

antife-yinyue commented 8 years ago

什么是跨域的描述太表面,可以继续深挖。

对于代码的解读,应该直接贴代码,只说第几行根本不知道在说啥。代码一旦变动,行号就对应不上了

brunoyang commented 8 years ago

ok 已更新