什么是同源策略

同源策略(Same-Origin Policy)并不是单单指某一种策略,而是浏览器的一种安全机制的统称,用来限制不同域名之间文档、脚本的互相访问。所谓同源,就是两个 URL 的协议头(protocol)、域名(host)与端口号(port)相同,否则就是不同源。比如 http://store.company.com/dir2/other.htmlhttp://store.company.com/dir/inner/another.html 同源,与 https://store.company.com/page.htmlhttp://store.company.com:81/dir/page.html 都不同源。 尽管不同 API 的同源策略可能有些许不同,但是总体上都是为了防止用户在访问不可信网站时,阻止这些不可信网站访问可信网站的用户会话等数据。 举个例子,用户浏览器可能同时打开了银行、淘宝、微博等网站,浏览器本地保存了用户在这些网站的会话信息,此时当用户访问一个不可信网站时,同源策略会阻止这个不可信网站读取浏览器本地保存的这些可信网站的数据,以减少攻击可能。

在网络请求中,同源策略在发送数据和接收数据时表现有所不同。一般来说,一个源(one origin)可以给另一个源(another origin)发送数据,但是一个源不可以从另一个源读取数据。这样做是为了防止恶意网站读取可信网站的数据,但是同时这也阻止了两个可信网站之间的数据读取。即使在同源策略下,跨站(cross-site)发送数据也不是完全安全的,因为这可能导致跨站请求伪造(csrf)和点击劫持(clickjacking)。

跨域访问

同源策略一定程度上减少了恶意网站的攻击,但是也限制了不同源的可信站点之间的数据交流。以下介绍几种常见的跨域访问形式。

修改源(origin)

一个页面可以通过 document.domain 修改自身的源,但是只能修改为该页面的当前域名或者当前域名的父级域名。如果 document.domain 被修改成父级域名,那么浏览器在进行同源检测时会使用父级域名进行检测。比如,位于 http://store.company.com/dir/other.html 页面的脚本,通过

document.domain = "company.com";
1

的方式修改了当前域名之后,就可以通过与页面 http://company.com/dir/page.html 之间的同源检测。

这种做法有一个前提条件,就是父级域名的页面也通过相同的方式设置了相同的域名。因为通过 document.domain 修改源时,浏览器会将端口号设置为 null,因此如果单独将 other.html 的 document.domain 设置为 company.com 的话,此时页面的端口号为 null,而 page.html 的端口号为 80,依然无法通过同源检测。

JSONP

JSONP 是常用方法跨域方法,最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。JSONP 的原理是网页通过添加一个 <script> 元素,向服务器请求 JSON 数据,这种做法不受同源策略限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。 比如,客户端通过如下代码请求 company.com 域下的服务,

function loadCrossSiteData(src) {
  var script = document.createElement("script");
  script.setAttribute("type", "text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  loadCrossSiteData("http://company.com/item?callback=logDetail");
};

function logDetail(data) {
  console.log("Item detail is: " + data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在服务端,返回如下代码。因为 <script> 标签请求的脚本,直接作为代码运行,因此只要页面中定义了 logDetail 函数,该函数就会被立即调用,打印相关数据。

logDetail({ name: "itemName" });
1

JSONP 由于是通过 <script> 标签的方式实现跨域,因此只能完成 GET 方法的跨域,无法实现 POST 等方法的跨域。

CORS

CORS 是跨域资源共享(Cross-origin resource sharing)的缩写,它允许浏览器向跨域服务发送 AJAX 请求,从而克服了同源限制。

下图是 CORS 各个浏览器的支持情况。 IMAGE

CORS 需要浏览器和服务器同时支持。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,再根据服务端响应的结果判断是否完成请求,请求期间用户不会有任何感觉。因此,实现 CORS 通信的关键是服务器,一般来说,只要服务器实现了 CORS 接口,就可以跨域通信。

简单来说,当浏览器发现需要向跨域服务发送请求时,会先发一个预检请求(preflight)。该请求是一个 OPTIONS 请求,用来询问服务端是否可跨域,请求头中会携带 OriginAccess-Control-Request-Method 等字段。服务器收到预检请求以后,检查了 Origin、Access-Control-Request-Method 字段以后,确认允许跨源请求,就可以做出响应,响应中包含如下请求头,Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Origin 表明允许哪些域跨域访问,如果为 * 号则表示允许任何域访问。预检请求通过后,浏览器就会向普通 AJAX 请求一样请求数据,但是会携带上 Origin 请求头,同时服务器的响应也会带上 Access-Control-Allow-Origin响应头。

CORS 相对于 JSONP 来说更加彻底,可以实现任何 HTTP 方法的跨域访问,是浏览器原生支持的跨域方法。但是比 JSONP 多一次请求,对服务器性能有轻微影响,同时对浏览器版本也有一定的要求。

参考链接

关注微信公众号,获取最新推送~

加微信,深入交流~