浏览器和服务器实现跨域(CORS)断定的原理

前端对Cross-Origin Resource Sharing 问题(CORS,中文又称'跨域')应该很熟悉了。众所周知出于安全的考虑,浏览器有个同源策略,对于不一样源的站点之间的相互请求会作限制(跨域限制是浏览器行为,不是服务器行为。)。不过下午想到了一个略无趣的问题:浏览器和服务器究竟是如何断定有没有跨域呢?本文主要分两个部分,一是对这个问题的总结,二是nginx下如何配置服务器容许跨域。
<!-- more -->javascript

同源策略

同源指的是域名(或IP),协议,端口都相同,不一样源的客户端脚本(javascript、ActionScript)在没明确受权的状况下,不能读写对方的资源。html

同源的断定:
http://www.example.com/dir/page.html为例,如下表格指出了不一样形式的连接是否与其同源:(缘由里未申明不一样的属性即说明其与例子里的原连接对应的属性相同)前端

连接 结果 缘由
http:// www.example.com /dir/page2.html 同协议同域名同端口
http:// www.example.com /dir2/other.html 同协议同域名同端口
http://user:pwd@ www.example.com/dir2/other.html 同协议同域名同端口
http://www.example.com: 81/dir/other.html 端口不一样
https://www.example.com/dir/other.html 协议不一样端口不一样
http:// en.example.com/dir/other.html 域名不一样
http:// example.com/dir/other.html 域名不一样(要求精确匹配)
http:// v2.www.example.com/dir/other.html 域名不一样(要求精确匹配)
http://www.example.com: 80/dir/other.html 不肯定 取决于浏览器的实现方式

是否容许跨域的断定

前文提到了同源策略的断定,然而同源策略在增强了安全的同时,对开发倒是极大的不便利。所以开发者们又发明了不少办法来容许数据的跨域传输(常见的办法有JSONPCORS)。当域名不一样源的时候,因为跨域实现的存在,浏览器不能直接根据域名来断定跨域限制。那么浏览器具体又是如何实现断定的呢?看下面的例子。html5

环境说明

  1. 参与实验的前端域名三个有:http://www.zhihu.comhttp://segmentfault.com
    http://localhostjava

  2. 请求的服务器端地址为http://localhost/city.json,服务器解析引擎使用的nginx,且服务器只配置了容许来自http://segmentfault.com的跨域请求nginx

  3. 检测方法:在各个域名下利用chrome浏览器的console界面模拟发送ajax请求,代码以下:ajax

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://localhost/city.json',true);
    xhr.send();

实验过程

  1. http://localhost域名下,请求成功。chrome

    服务器回应的http文件头以下:
    HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Sun, 05 Jul 2015 17:44:06 GMT
    Content-Type: application/octet-stream
    Content-Length: 2084
    Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
    Connection: keep-alive
    ETag: "5531f79c-824"
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Accept-Ranges: bytes
  2. http://segmentfault.com域名下,请求成功json

    服务器回应的http文件头以下:
    HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Sun, 05 Jul 2015 18:17:27 GMT
    Content-Type: application/octet-stream
    Content-Length: 2084
    Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
    Connection: keep-alive
    ETag: "5531f79c-824"
    **Access-Control-Allow-origin: http://segmentfault.com**
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Accept-Ranges: bytes
  3. http://www.zhihu.com下,请求失败
    虽然都是失败,可是返回的HTTP文件头内容会视服务器是否有配置跨域请求而发生变化segmentfault

服务器容许跨域请求

(仅容许来自http://segmentfault.com的跨域请求)
console.log窗口提示:

XMLHttpRequest cannot load http://localhost/city.json. The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin. Origin 'http://www.zhihu.com' is therefore notallowed access.

服务器回应的http文件头以下:

HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Sun, 05 Jul 2015 17:59:25 GMT
    Content-Type: application/octet-stream
    Content-Length: 2084
    Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
    Connection: keep-alive
    ETag: "5531f79c-824"
    Access-Control-Allow-origin: http://segmentfault.com
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Accept-Ranges: bytes

服务器不容许任何跨域请求

console.log窗口提示:

XMLHttpRequest cannot load http://localhost/city.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.zhihu.com' is therefore not allowed access.

服务器回应的http文件头以下:

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Sun, 05 Jul 2015 17:51:29 GMT
Content-Type: application/octet-stream
Content-Length: 2084
Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
Connection: keep-alive
ETag: "5531f79c-824"
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, OPTIONS
Accept-Ranges: bytes

跨域的断定流程

zhihu页面的两次浏览器报错以及segmentfault的成功返回值来看,能够很容易得出浏览器和服务器的合做断定步骤以下:

  1. 浏览器先根据同源策略对前端页面和后台交互地址作匹配,若同源,则直接发送数据请求;若不一样源,则发送跨域请求。

  2. 服务器解析程序收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何容许跨域,则文件头里不包含Access-Control-Allow-origin字段,若配置过域名,则返回Access-Control-Allow-origin+ 对应配置规则里的域名的方式

  3. 浏览器根据接受到的http文件头里的Access-Control-Allow-origin字段作匹配,若无该字段,说明不容许跨域;如有该字段,则对字段内容和当前域名作比对,若是同源,则说明能够跨域,浏览器发送该请求;若不一样源,则说明该域名不可跨域,不发送请求

(可是不能仅仅根据服务器返回的文件头里是否包含Access-Control-Allow-origin来判断其是否容许跨域,由于服务器端配置多域名跨域的时候,也会出现不能跨域的域名返回包里没有Access-Control-Allow-origin字段的状况。下文配置说明里会讲。)

配置服务器实现跨域传输

前面讲到了同源策略的基本断定,以及浏览器实现跨域判断的方式,那么,如何在服务器端作配置来容许跨域传输呢?下文将以Nginx为例,讲一下三种状况下的配置。

配置项解析

CORS经常使用的配置项有如下几个:

  • Access-Control-Allow-Origin(必含) – 容许的域名,只能填通配符或者单域名

  • Access-Control-Allow-Methods(必含) – 这容许跨域请求的http方法(常见有POSTGETOPTIONS

  • Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) – 这是对预请求当中Access-Control-Request-Headers的回复,和上面同样是以逗号分隔的列表,能够返回全部支持的头部。

  • Access-Control-Allow-Credentials(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)。若是不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest2对象当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true;withCredentials为false时,省略该项不写。反之则致使请求失败。

  • Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。预请求的的发送并不是免费午饭,容许时应当尽量缓存。

具体配置举例

全域名或者单域名容许跨域

这个最省心
打开Nginx的配置文件(默认为nginx.conf)。找到对应域名设置的local配置部分。
添加如下内容:

add_header 'Access-Control-Allow-origin' 'http://www.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';

添加的域名必须带http://协议头(不然服务器没法区分是http仍是https),若是接受全部域名的跨域请求,则能够用*(安全性有问题,不推荐)

添加多域名跨域配置

若是容许跨域的域名有多个但出于安全问题又不想配置全域名通配的时候,就能够用到nginx里的if判断了。
添加以下内容:

if ($http_origin = 'http://segmentfault.com' ) {  
 add_header 'Access-Control-Allow-Origin' "$http_origin";
 }
if ($http_origin = 'http://localhost:4000' ) {  
 add_header 'Access-Control-Allow-Origin' "$http_origin";
 }
 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
  • 若是对正则比较熟悉的,能够直接用正则来匹配条件判断,不须要用if这么麻烦的方式。

  • 'Access-Control-Allow-Methods' 容许多参数,'Access-Control-Allow-origin'不容许多参数,因此只能是条件语句判断要不要加这个。这也是我前面提到的为何即便HTTP文件头返回值里没有'Access-Control-Allow-origin',也不能说明它就是不容许跨域的。

  • nginx配置文件的http配置部分不能用if条件语句,因此多域名的时候必须加在local部份内。另外加在local内的只对对应的服务器域名作跨域请求的配置,加在http里会让跑在该nginx下的全部网站都统一采起这种配置。

  • Access-Control-Allow-Origin也能够改为全小写的形式,不影响结果.(access-control-allow-origin也能够)

PS:
这篇文章写的可能有点绕,另外因为运维方面和WEB安全方面不是特别熟悉,因此后面配置那里未必是最优解,恳请看到的各位指点。

参考文献

  1. Same-origin policy

  2. HTTP access control (CORS)

  3. Using CORS

  4. 利用CORS实现跨域请求 (是参考文献2的中译版,配置参数解析那里有所参考)

  5. enable cross-origin resource sharing

相关文章
相关标签/搜索