前面的话
script、image、iframe的src都不受同源策略的影响。所以可以借助这一特点,实现跨域。例如,前面介绍的jsonp是使用script标签,imgPing是使用image标签,而本文将介绍使用iframe标签实现跨域
引入
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是”三个相同“:1、协议相同;2、域名相同;3、端口相同
举例来说,http://www.example.com/dir/page.html
这个网址,协议是http://
,域名是www.example.com
,端口是80
(默认端口可以省略)。它的同源情况如下
http://www.example.com/dir2/other.html:同源 http://example.com/dir/other.html:不同源(域名不同) http://v2.www.example.com/dir/other.html:不同源(域名不同) http://www.example.com:81/dir/other.html:不同源(端口不同) https://www.example.com/dir/page.html:不同源(协议不同)
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A 网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取 A 网站的 Cookie,会发生什么?很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,“同源政策”是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了
随着互联网的发展,“同源政策”越来越严格。目前,如果非同源,共有三种行为受到限制
1、Cookie、LocalStorage 和 IndexedDB 无法读取
2、DOM 无法获得
3、AJAX 请求无效(可以发送,但浏览器会拒绝接受响应)
虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响
iframe
iframe
元素可以在当前网页之中,嵌入其他网页。每个iframe
元素形成自己的窗口,即有自己的window
对象。iframe
窗口之中的脚本,可以获得父窗口和子窗口。但是,只有在同源的情况下,父窗口和子窗口才能通信;如果跨域,就无法拿到对方的DOM
[注意]关于iframe的详细信息移步至此
比如,父窗口和子窗口的代码如下所示,都处于localhost域下
<!-- 父窗口test.html--> <body> <iframe id="myIFrame" src="iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); iframe.onload = function(){ var doc = iframe.contentWindow.document; console.log(doc.getElementById('test').innerHTML);//'xiaohuochai' console.log(document.cookie);//'name=match' } </script> </body> <!-- 子窗口iframe.html--> <body> <div id="test">xiaohuochai</div> <script> document.cookie = 'name=match'; </script> </body>
如果iframe
窗口不是同源,如处于文件域下(file:///C:/Users/Administrator/Desktop/demo/js/test.html),就会报错
<iframe id="myIFrame" src="iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); iframe.onload = function(){
//Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame. console.log(iframe.contentWindow.document); } </script>
上面命令中,父窗口想获取子窗口的DOM,因为跨域导致报错。
反之亦然,子窗口获取主窗口的DOM也会报错。
window.parent.document.body // 报错
这种情况不仅适用于iframe
窗口,还适用于window.open
方法打开的窗口,只要跨域,父窗口与子窗口之间就无法通信
domain属性
如果两个窗口一级域名相同,只是二级域名不同,可以通过设置document.domain来使其通信
父窗口地址为https://static.xiaohuochai.site/test/test.html
子窗口地址为https://demo.xiaohuochai.site/test/iframe.html
代码如下
<!-- 父窗口test.html--> <body> <iframe id="myIFrame" src="https://demo.xiaohuochai.site/test/iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); iframe.onload = function(){ var doc = iframe.contentWindow.document; console.log(doc.getElementById('test').innerHTML);//'xiaohuochai' console.log(document.cookie); } </script> </body> <!-- 子窗口iframe.html--> <body> <div id="test">xiaohuochai</div> <script> document.cookie = 'name=match'; </script> </body>
由结果所示,通过设置document.domain只能获取DOM,而Cookie、LocalStorage 和 IndexedDB 无法读取
锚点值
锚点值,又称为片段标识符(fragment identifier),指的是URL的#
号后面的部分,比如http://example.com/x.html#fragment
的#fragment
。如果只是改变片段标识符,页面不会重新刷新
父窗口可以把信息,写入子窗口的锚点值
var src = originURL + '#' + data; document.getElementById('myIFrame').src = src;
子窗口通过监听hashchange
事件得到通知
window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... }
同样的,子窗口也可以改变父窗口的片段标识符
parent.location.href= target + '#' + hash;
下面是具体代码
<!-- 父窗口test.html--> <body> <iframe id="myIFrame" src="iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); window.onhashchange = function (e) { console.log(/.*#(.*)/g.exec(e.newURL)[1])//'xiaohuochai' } </script> </body> <!-- 子窗口iframe.html--> <body> <div id="test">xiaohuochai</div> <script> parent.location.href = 'test.html' + '#' + test.innerHTML; </script> </body>
XDM
上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API
[注意]IE8-浏览器不支持
跨文档消息传送(cross-document messaging),有时候简称为XDM,指的是在来自不同域的页面间传递消息。例如,www.wrox.com域中的页面与位于一个内嵌框架中的p2p.wrox.com域中的页面通信。 在XDM机制出现之前,要稳妥地实现这种通信需要花很多工夫。XDM把这种机制规范化,让我们能既稳妥又简单地实现跨文档通信
XDM的核心是postMessage ()方法。在HTML5规范中,除了 XDM部分之外的其他部分也会提到这个方法名,但都是为了同一个目的:向另一个地方传递数据。对于XDM而言,“另一个地方”指的是包含在当前页面中的<iframe>元素,或者由当前页面弹出的窗口
postMessage()方法接收两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。第二个参数对保障安全通信非常重要,可以防止浏览器把消息发送到不安全的地方
来看下面的例子。
//注意:所有支持XDM的浏览器也支持ifraaie的contentWindow属性 var iframeWindow = document.getElementById("rayframe").contentWindow, iframeWindow.postMessage( "A secret', "http://www.wrox.com");
最后一行代码尝试向内嵌框架中发送一条消息,并指定框架中的文档必须来源于"http://www.wrox.com"域。如果来源匹配,消息会传递到内嵌框架中;否则,postMessage()什么也不做。 这一限制可以避免窗口中的位置在你不知情的情况下发生改变。如果传给postMessage()的第二个参数是"*",则表示可以把消息发送给来自任何域的文档,但不推荐这样做
接收到XDM消息时,会触发window对象的message事件。这个事件是以异步形式触发的,因此从发送消息到接收消息(触发接收窗口的message事件)可能要经过一段时间的延迟。触发message事件后,传递给onmessage处理程序的事件对象包含以下三方面的重要信息
data:作为postMessage()第一个参数传入的字符串数据 origin:发送消息的文档所在的域,例如"http://www.wrox.com"。 source:发送消息的文档的window对象的代理。这个代理对象主要用于在发送上一条消息的窗口中调用postMessage()方法。如果发送消息的窗口来自同一个域,那这个对象就是window
接收到消息后验证发送窗口的来源是至关重要的。就像给postMessage()方法指定第二个参数, 以确保浏览器不会把消息发送给未知页面一样,在onmessage处理程序中检测消息来源可以确保传入的消息来自已知的页面。基本的检测模式如下
window.onmessage = function(e){ if(e.origin == 'http://www.wrox.com'){ //处理接收到的数据 processMessage(e.data); //可选:向来源窗口发送回执 e.source.postMessage("Received!", "http//p2p.wrox.com"); } }
[注意]event.source大多数情况下只是window对象的代理,并非实际的window对象。换句话说,不能通过这个代理对象访问window对象的其他任何信息。只通过这个代理调用 postMessage()就好,这个方法永远存在,永远可以调用
XDM还有一些怪异之处
postMessage()的第一个参数最早是作为“永远都是字符串”来实现的。但后来这个参数的定义改了,改成允许传入任何数据结构。可是,并非所有浏览器都实现了这一变化。为保险起见,使用postMessage()时,最好还是只传字符串。如果想传入结构化的数据,最佳选择是先在要传入的数据上调用JSON.stringify(),通过postMessage()传入得到的字符串,然 后再在onmessage事件处理程序中调用JSON.parse()
在通过内嵌框架加载其他域的内容时,使用XDM是非常方便的。因此,在混搭(mashup)和社交网络应用中,这种传递消息的方法极为常用。有了XDM,包含<iframe>的页面可以确保自身不受恶意内容的侵扰,因为它只通过XDM与嵌入的框架通信。而XDM也可以在来自相同域的页面间使用
下面是一个实例
父窗口地址为https://static.xiaohuochai.site/test/test_1.html
子窗口地址为https://demo.xiaohuochai.site/test/iframe_1.html
代码如下
<!-- 父窗口test_1.html--> <body> <iframe id="myIFrame" src="https://demo.xiaohuochai.site/test/iframe_1.html"></iframe> <script> window.onmessage = function (e) { if(e.origin === 'https://demo.xiaohuochai.site'){ console.log(e.data);//'xiaohuochai' } } </script> </body> <!-- 子窗口iframe_1.html--> <body> <script> if (window.parent !== window.self) { window.parent.postMessage('xiaohuochai', 'https://static.xiaohuochai.site'); } </script> </body>