同源策略及跨网页通信和跨域问题

576

同源策略及跨网页通信和跨域问题

概念

同源:两个网页的协议、域名、端口都完全相同。
// 非同源,域名也必须完全相同
http://www.baidu.com
http://a.baidu.com
同一站点:根域名+协议相同

根域名相同:子域名相同+不同端口

// 同一站点
https://time.geekbang.org
https://www.geekbang.org

目的

保护用户信息安全,防止恶意网站窃取信息

限制范围

  • Cookie\LocalStorage\IndexDB无法读取
  • DOM无法获得
  • AJAX请求不能发送

解决方法

二级域名相同

  1. Cookie(二级域名相同)

    当两个网页二级域名相同时,可通过设置document.domain共享Cookie(不适用于LocalStorage、IndexDB)

    顶级域名=一级域名=.com/.cn/.org

    N级域名=N-1级域名前追加一级

    // 网页A http://w1.example.com/a.html
    // 网页B http://w2.example.com/b.html
    // 二级域名相同,在两个网页中设置相同的domain,可共享Cookie
    document.domain = example.com
    
  2. Iframe(二级域名相同)

    iframe窗口和window.open打开的窗口和父窗口不同源时,不能获取其Dom元素

    当二级域名相同时,通过设置document.domain可解决

完全不同源(跨域/跨页面通信)

  1. 片段标识符(hash值)

    已知:hash值改变不会导致页面的刷新,也不会发送请求

    当子窗口与父窗口域名完全不同时,通过将需要的数据设置为hash值即可传递。

    // 父窗口传子窗口
    // 已知父窗口和子窗口的域名,改变hash值页面不会刷新,也不会发送请求
    // 在父窗口中设置hash
    var src = originUrl + '#' +data; // originUrl为子窗口域名
    document.getElementById('myFrame').src = src;
    ​
    // 子窗口中监听hashChange事件,当hash值改变时触发获取数据
    window.hashChange = function () {
        const data = window.loacation.hash;
    }
    
  2. window.name

    已知:window.name属性在同一个窗口中的不同域或不同窗口中都是相同的

    已知:当iframesrc与页面的src是同源时,才能获取这个dom元素

    利用iframesrc可以跨域的特性,从不同源的服务器中获取到数据后,设置为其window.name的值,再改变iframesrc为当前页面的同源域,获取数据。

    // 当前页面
    var iframe = document.createElement('iframe');
    iframe.src = 'http://localhost/data.php'; // 服务器中设置window.name = data
    document.body.appendChild(iframe);
    var state = 0;
    // 监听iframe加载,当第一次加载完成后更改为同源域再获取data
    iframe.onload = function () {
        if(state === 0) {
            state = 1;
            iframe.contentWindow.loacation= 'http://localhost:81/cross-domain/proxy.html';
        } else if (state === 1) {
            var data = JSON.parse(iframe.contentWindow.name);
        }
    }
    
  3. window.postMessage(主要用于不同源页面通信,LocalStorage通信)

    H5新API,无论同不同源的窗口,都可以互相通信。

    // otherWindow: 其他窗口的引用,比如iframe.contentWindow/window.open()返回的窗口对象/window.frames[1]
    // message: 发送的数据
    // targetOrigin: 发送窗口的url或*
    // transfer: 可选,传递的Transferable对象
    otherWindow.postMessage(message, targetOrigin, [transfer]);
    

    其他window中通过监听message事件,从event对象中获取数据

    window.addEventListener('message', (event) => {
        // event.data: 传递的对象
        // event.origin: 发送窗口的url
        // event.source: 发送窗口对象的引用,可用于向发送窗口传递数据
    }, false)
    

    通过window.postMessage()将子窗口的LocalStorage值传给父窗口

    // 父窗口(http://aaa.com)中嵌入iframe,指向子窗口(http://bbb.com)
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = { name: 'Jack' };
    // 存入对象
    win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://bbb.com');
    // 读取对象
    win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
    window.onmessage = function(e) {
      if (e.origin != 'http://aaa.com') return;
      // "Jack"
      console.log(JSON.parse(e.data).name);
    };
    
    // 子窗口(http://bbb.com)
    window.onmessage = function () => {
      if (e.origin !== 'http://bbb.com') return;
      var payload = JSON.parse(e.data);
      switch (payload.method) {
        case 'set':
          localStorage.setItem(payload.key, JSON.stringify(payload.data));
          break;
        case 'get':
          var parent = window.parent;
          var data = localStorage.getItem(payload.key);
          // 向父窗口发送数据
          parent.postMessage(data, 'http://aaa.com');
          // e.source.postMessage(data, 'http://aaa.com')  向发窗口发送数据2 
          break;
        case 'remove':
          localStorage.removeItem(payload.key);
          break;
      }
    }
    
  4. ajax跨域问题

    cors跨域、JSONP跨域、Nginx代理、window.name+iframe

参考资料

  1. www.ruanyifeng.com/blog/2016/0…
  2. developer.mozilla.org/zh-CN/docs/…

\