# 1. 从跨域到 CORS #### 1. 介绍 跨域,可能很多前端开发者都会遇到过,也可能知道有jsonp,iframe之类的跨域方法。不过要说这些方法之前,先得来说说什么叫跨域,为什么要跨域。 所谓跨域,顾名思义,跨到了另外的域,域不仅仅指的是不同的域名网站,可能同一个域名不同的端口号也算不同的域。浏览器是有规则的,只要协议、域名、端口有任何一个不同,都被当作是不同的域。协议指的是http,或者https等。 下面给出一个列表,指出不同域的情况: ![](https://box.kancloud.cn/ea0f3b97e79986064c3afaffdbe6e1b4_532x203.png) 这个叫浏览器的同源策略(same-origin policy),为什么要这样规定呢,原因之一就是为了安全。 假如你在A网站,用一段js脚本就能访问B网站,B网站凭什么让你访问,为什么浏览器能让你随便访问别的网站呢,说不定B网站存在着各种危险,比如盗取你的密码,跨域攻击(CSRF)等,一切都不太合理。 但也是有例外的情况,有些情况是要访问到别的网站的,比如加载一张图片,这张图片可能在别的网站,还有加载一个js文件,也是有可能在别的网站的,还有嵌入一个frame元素,也可以访问到别的网站的内容。 所以跨域正是利用了上面几点,比如jsonp就是利用的加载js文件的功能,比如在`<script src="..."></script>`中的src指定为目标网站的js,同理,还有跨域攻击也可以利用`<image src="..." />`的功能。还有iframe更能直接加载别的网站的内容到自己的网站里来。 这前面几种可以说是技巧,说句不好听的就是浏览器的漏洞,浏览器并未从正面上支持跨域,而且上面的几种跨域方法也是有各种局限,比如jsonp的方法,只能用GET方法,iframe方法是能直接加载内容到网站上,但是本网站和iframe的数据交互也是一个头疼的地方。 毕竟从iframe加载内容到本站后,是存在着数据交互的,可以用`document.domain`,`window.name`,不过这些方法都能用,且有效,不过总不尽完美,存在着各种各样的局限。 然而HTML5引进了一个叫window.postMessage方法来跨域传送数据,这个倒是不错,不过也是利用了iframe。 除此之外,还有flash,服务器代理等跨域方法,但是本章要介绍的是浏览器或服务器的跨域方法,它的名字叫CORS。 #### 2. CORS CORS全称是Cross-Origin Resource Sharing,跨域资源共享,这是浏览器的标准,也算是协议,基本上现代浏览器都支持,除了奇葩浏览器,例如IE8、IE9,只支持部分特性。 使用它,需要服务器端和客户端两方面的准备,服务器端我们选择nginx作为测试,客户端只是js罢了。 nginx服务监听在localhost的8080端口,而现在有一个网站运行localhost的3000端口,需要跨域到nginx那台服务器。 现在开始测试之旅,要在浏览器模拟跨域请求,只需三行js代码。 ``` var xhttp = new XMLHttpRequest(); xhttp.open("GET", "http://localhost:8080", true); xhttp.send(); ``` 我使用的浏览器是chrome,打开它的开发者工具,在`console`里运行上面的代码,效果如下: ![](https://box.kancloud.cn/02ac0d9622f5c2284a3371499635c5aa_1300x576.png) 上面的报错已经提示得很明显了,主要是下面这句话: ``` XMLHttpRequest cannot load http://localhost:8080/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. VM162:4 XHR failed loading: GET "http://localhost:8080/". ``` 错误中说`Access-Control-Allow-Origin`这个头信息不存在于被请求的服务器中,来源域`http://localhost:3000`是不允许访问该服务器的。 `XMLHttpRequest`这个可能很多开发者都明白,那是ajax请求利用的对象,利用它能发起ajax请求,但它的功能不仅仅是发起ajax请求,还能用于跨域,还有设置时限,FormData对象管理表单数据,文件上传等功能,具体可以自行搜索相关的资料,在这里,它能发起跨域请求就可以了。 我们来看看这个请求相关的信息。 ![](https://box.kancloud.cn/3aacc1964e57cea61bd1c87ca8ae3c86_1329x507.png) 具体的头信息如下: ``` Request Headers Accept:*/* Accept-Encoding:gzip, deflate, sdch Accept-Language:en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4 Connection:keep-alive Host:localhost:8080 Origin:http://localhost:3000 Referer:http://localhost:3000/ User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36 ``` 最重要的是`Origin:http://localhost:3000`这一行,它标明的是来源的域,这是请求头信息,会传给服务器。 我们来看下服务器。 ![](https://box.kancloud.cn/02bbcc8a9a7c9f7883ec91ac974d2a4e_744x257.png) 可见,服务器还是正常响应200状态的。也就是说,服务器端该怎么应答就怎么应答,只是被浏览器给阻止了。 浏览器是如何阻止的呢。主要是看响应头部的信息。 ``` Response Headers Accept-Ranges:bytes Connection:keep-alive Content-Length:612 Content-Type:text/html Date:Mon, 01 Feb 2016 07:17:54 GMT ETag:"56988bc6-264" Last-Modified:Fri, 15 Jan 2016 06:03:50 GMT Server:nginx/1.8.0 ``` 它没有发现有发现`Origin:http://localhost:3000`这个来源域名是被服务器所允许的。 我们在服务器端设置一下,让`Origin:http://localhost:3000`这个来源域被允许访问。 ``` location / { add_header 'Access-Control-Allow-Origin' '*'; } ``` ``add_header 'Access-Control-Allow-Origin' '*'`表示允许任何来源域访问nginx这台服务器。 用`sudo nginx -s reload`重新加载服务器配置。 再来看下效果。 ![](https://box.kancloud.cn/8a4d9589e6d4da3f57a3e32c6af74bee_646x197.png) 果然成功了,不再提示错误。 来看下响应的信息。 ``` Response Headers Accept-Ranges:bytes Access-Control-Allow-Origin:* Connection:keep-alive Content-Length:612 Content-Type:text/html Date:Mon, 01 Feb 2016 07:25:25 GMT ETag:"56988bc6-264" Last-Modified:Fri, 15 Jan 2016 06:03:50 GMT Server:nginx/1.8.0 ``` 响应信息中多了这一行`Access-Control-Allow-Origin:*`。 原来,浏览器在用`XMLHttpRequest`发起跨域请求的时候,它在请求头带了`Origin`这个项,而服务器,在响应头信息中是有响应`Access-Control-Allow-Origin`这项的,两者比较一下,如果匹配,则请求成功,不匹配就不成功,不过,服务器那边还是照常执行。 **在测试的时候需要注意的事,有可能会发现改了nginx的配置,浏览器发出的跨域请求却没生效,这可能是因为浏览器cache的原因,只要清除浏览器的cache,再重新发起请求就好了。** 下一节[CORS进阶之Preflight请求(二)](http://www.rails365.net/articles/cors-jin-jie-zhi-preflight-qing-qiu-er)会介绍CORS更高阶的内容,比如`Preflight请求`,`Access-Control-Allow-Methods`,`Access-Control-Allow-Headers`等。 完结。