同源策略及跨网页通信和跨域问题
概念
同源:两个网页的协议、域名、端口都完全相同。
// 非同源,域名也必须完全相同
http://www.baidu.com
http://a.baidu.com
同一站点:根域名+协议相同
根域名相同:子域名相同+不同端口
// 同一站点
https://time.geekbang.org
https://www.geekbang.org
目的
保护用户信息安全,防止恶意网站窃取信息
限制范围
- Cookie\LocalStorage\IndexDB无法读取
- DOM无法获得
- AJAX请求不能发送
解决方法
二级域名相同
-
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
-
Iframe(二级域名相同)
当
iframe
窗口和window.open
打开的窗口和父窗口不同源时,不能获取其Dom元素当二级域名相同时,通过设置
document.domain
可解决
完全不同源(跨域/跨页面通信)
-
片段标识符(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; }
-
window.name
已知:
window.name
属性在同一个窗口中的不同域或不同窗口中都是相同的已知:当
iframe
的src
与页面的src
是同源时,才能获取这个dom
元素利用
iframe
的src
可以跨域的特性,从不同源的服务器中获取到数据后,设置为其window.name
的值,再改变iframe
的src
为当前页面的同源域,获取数据。// 当前页面 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); } }
-
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; } }
-
ajax跨域问题
cors跨域、JSONP跨域、Nginx代理、window.name+iframe
参考资料
\