同源策略

同源策略(Same origin policy)是由Netscape提出的一种安全策略约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。

定义与说明

定义

所谓同源是指,域名协议端口相同。当浏览器的某个页面执行或者请求某个资源或者脚本时,如果请求路径非同源,浏览器会在控制台中报告一个异常,提示拒绝访问。

目的

保证用户信息的安全,防止恶意的网站窃取数据。特别是对于存有大量用户个人信息的cookie而言,如果其他网站可以随意访问,这将是极大的安全隐患。

限制范围

目前,如果非同源,共有三种行为受到限制:

  • Cookie、LocalStorage 和 IndexDB 无法读取;
  • DOM 无法获得;
  • AJAX 请求不能发送。

优点与缺点

优点

  1. 阻止不可靠的数据源;
  2. 防止数据被恶意窃取;
  3. 防止数据被恶意篡改。

缺点

  1. 用户合理的跨域请求被拒绝;
  2. 对于前后端分离的应用开发,只能在服务器上进行调试。

跨域访问

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。

  • 方法一
    如果两个网页二级域名相同,只是三级域名不同,浏览器允许通过设置document.domain共享 Cookie。
    例如:a.huangyufeng.comb.huangyufeng.com,只需将document.domain设置成huangyufeng.com即可共享同一个Cookie:

    1
    document.domain = 'huangyufeng.com';
  • 方法二
    与方法一原理相同,只是不通过客户端设置domain,而是在服务端设置Cookie值时添加对domain的设定:

    1
    Set-Cookie: key=value; domain=.huangyufeng.com; path=/

注:

  1. doucument.domain的值只能从tight状态转向loose状态的域名,即只能从长级域名转向相对短级的域名;
  2. 这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法规避同源政策。

iframe

如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。

对于iframe跨域,有四种方法:

  • document.domain
    如果两个窗口之间二级域名相同,而三级域名或更高级域名不同时,可以设置document.domain来规避同源策略。同Cookie跨域中的方法一。
  • 片段标识符(fragment identifier)
    片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://huangyufeng.com/#fragment#fragment。如果只是改变片段标识符,页面不会重新刷新。
    父窗口可以把信息写入子窗口的片段标识符:
    1
    2
    var src = originURL + '#' + data;
    document.getElementById('myIFrame').src = src;
    子窗口通过监听hashchange事件得到通知:
    1
    2
    3
    4
    5
    6
    window.onhashchange = checkMessage;

    function checkMessage() {
    var message = window.location.hash;
    // ...
    }
    同样的,子窗口也可以改变父窗口的片段标识符:
    1
    parent.location.href= target + "#" + hash;
  • window.name
    window.name的最大特点是无论是否同源,在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
    子窗口将数据写入window.name:
    1
    window.name = 'data';
    父窗口读取子窗口数据:
    1
    var data = document.getElementById('myFrame').contentWindow.name;

    这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

  • 跨文档通信API(Cross-document messaging)
    HTML5为了解决跨域问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
  1. window.postMessage
    API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源:
    发送数据:
    1
    window.postMessage(data, url); // url可为*
    接收数据:
    1
    2
    3
    4
    5
    6
    window.addEventListener('message', function(e) {
    console.log(e.data); // 接收到的数据
    console.log(e.source); // 数据源(发送数据的window对象)
    console.log(e.origin); // 数据目的地址,用于过滤发送给其他目标的数据
    e.source.postMessage('newdata', '*'); // 向数据源传递数据
    }, false);
  2. LocalStorage
    通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能:
    1
    2
    3
    4
    5
    6
    7
    window.onmessage = function(e) {
    if (e.origin !== 'http://huangyufeng.com') {
    return;
    }
    var payload = JSON.parse(e.data);
    localStorage.setItem(payload.key, JSON.stringify(payload.data));
    };

AJAX

同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制:

  • JSONP
    在web中通过<script>方式引入脚本的方式不受同源限制,因此,可以通过向web添加一个<script>标签,向服务器请求JSON数据,服务器收到请求后,将数据放在一个指定名字的回调函数里传回来:
    1
    2
    3
    4
    5
    6
    7
    8
     var script = document.createElement('script');
    script.setAttribute("type","text/javascript");
    script.src = 'http://huangyufeng.com/ip?callback=foo'; // callback指定回调函数
    document.body.appendChild(script);

    function foo(data) {
    data.attribute;
    }

    传入参数为JSON对象,不需要转换。
    JSONP只能适用于GET请求

  • WebSocket
    WebSocket请求头字段中有一个字段Origin用于指定请求源,因此规避了同源策略。
    详见:《计算机网络之WebSocket》
  • CORS
    CORS是一个W3C标准,全称是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
    详见:《同源策略规避之CORS》

补充

二级域名定义

在查阅各类资料时,我发现对于二级域名的定义出现了两种情况:

  1. huangyufeng.com为二级域名,.com为顶级域名(一级域名),以此类推;
  2. huangyufeng.com为一级域名,x.huangyufeng.com为二级域名,以此类推。

查阅维基百科ICCANWikiICCAN官方文档后可以认定,第一种理解方式是被标准认定的。

参考

《浏览器同源政策及其规避方法》——阮一峰
ICCAN
ICCANWiki