浏览器同源策略

什么是同源策略

wiki 上面是Wikipedia对同源策略的定义,简单来说同源策略就是限制不同的源(Domain)之间的交互的安全策略。想象如果没有同源策略的限制,你访问银行的网站A时的登录信息,在你访问另一个网站B的时候,B网站可以拿到A网站的信息,如果B网站有恶意的代码,就可以直接访问你的银行账户。 同源策略中的同源,是三个相同:

  • 协议相同
  • 域名相同
  • 端口号相同

http://www.example.com为例:

URL Outcome Reason
http://www.example1.com Failure 域名不同
http://www.example.com:8080 Failure 端口号不同
http://www.example.com/dir/page.html Same Origin 仅仅是path不同
https://www.example1.com Failure 协议不同

同源策略限制的范围

同源策略对跨源访问的控制可以分为三类;

  • 对跨域资源的写操作是可以,例如表单提交、重定向这些不受同源策略的限制
  • 一些替换元素(img、audio、video等)不受同源策略的限制
  • 不同源之间的资源读操作(获取cookie、localStorage、DOM、AJAX请求等)受到同源策略的限制

对于开发人员来说,第三条限制,跨域资源之间的读取受到限制,这个是最常遇见的问题,对于第二条中一些HTML原生标签不受跨域访问的限制,可以用来hack掉同源策略的限制,以下资源是不受同源策略限制的:

  • <script>标签
  • 外部CSS的加载<link rel="stylesheet" href="...">
  • <Img>标签
  • <video>和<audio>标签
  • <object>、<embed>(<applet>也不受同源策略限制,但该标签已经被废弃)
  • @font-face引用的字体(浏览器实现有差异,有些要同源)
  • <iframe>中嵌入的内容

如何突破同源策略的限制

Change Origin

在网页上可以通过设置document.domain设置当前页面所在的域,例如,在页面http://store.company.com/dir/page.html中的JS脚本执行了下面的代码:

document.domain = "company.com"

那么在当前网站上去获取http://www.company.com的网站上的资源是不受同源策略的限制的,但是这个改变domain的方法仅仅用于二级域名获取一级域名的资源的时候,你不能在http://www.company.comdomain改变为otherCompany.com

iframe

iframe在的父子窗口如果不是同源的话,在访问获取彼此的DOM节点或者读取数据的话,会受到同源策略的限制,无法访问。 iframe 父窗口所在的域为http://localhost:5500,其中子窗口所在的域为http://localhost:8080,这里父子窗口是同源的,所以彼此在获取资源的时候,会受到同源策略的限制: parent child 对于iframe额跨域问题,有三种解决办法:

  • 使用fragement(hash tag)
  • windows.name
  • postMessage

    Fragement

    这里的fragement指的是url#后面的部分,在父窗口中把信息作为hash写入到子窗口的src上。

    const originalSrc = document.getElementById('myFrame').src;
    const src = originalSrc + '#' + data;
    document.getElementById('myFrame').location.replace(src);

    因为url hash的改变不会引起页面的刷新,但是会触发onhashchange的事件,在子窗口中监听onhashchange的事件。

    function getData() {
      const data = window.location.hash;
      ......
    }
    window.onhashchange = getData

    同样,子窗口也可以通过同样的方式,向父窗口传递数据:

    parent.location.replace(parentUrl + "#" + data);

    window.name

    window.name在页面进行跳转前设置的值,在跳转后也可以访问到,我们可以借助window.name的这个特性来解决跨域问题,整个流程如下:

  • 在父窗口设置子iframesrc,在子窗口load成功后将data挂到window.name上,然后设置子窗口的location.href到与父窗口同域的页面
  • 跳转到同域的页面后,父窗口就可以通过window.name拿到跨域页面设置的数据了 例如:

    a.com/index.html  // {A}
    b.com/index.html  // {B}
    a.com/empty.html  // {C}

    我们在A中加载完成后,在A页面中设置srcB页面的url,在iframe加载成功后,将数据设置到window.name上,然后改变自己的location.hrefC页面,C页面加载成功后,A页面和B页面就是同源的了,这个时候A页面去子窗口上取window.name的值就不会因为跨域问题而报错了。 使用window.name的关键是在设置完window.name后,要将页面跳转到与父窗口同源的页面,这样主窗口才能访问子窗口上设置的数据

    window.postMessag

    html5提供了postMessage的机制,用于不同源之间的通信:

postMessage MDN

在父窗口中监听message事件 pm1 iframe窗口和父窗口不是同源的,通过postMessage将消息发送给父窗口。

JSONP

在浏览器中,script标签是可以从不同源的地址上加载而不受同源策略的限制,这就给我们提供了一种手段,绕过同源策略的限制,JSONP就是通过这种手段来达到跨域通信的,但是JSONP需要服务端的支持,将返回的JSON数据和url中的callback拼接起来,只要前端定义了这个函数,在浏览器看来,就是从服务端加载了一段带数据的函数调用。 JSONP0 JSONP1

CORS

CORS(cross-origin resource share)规范了跨域请求的标准,通过HTTP头让浏览器允许跨域请求的发送,这个话题,我会在下一篇博客细讲。

Refrence


兴趣遍地都是,坚持和持之以恒才是稀缺的


Written by@wang yao
I explain with words and code.

GitHub