跨域解决方案和实践

document.domian + iframe跨子域

这种方法对于主域相同而子域不同的例子,可以通过设置document.domain的办法解决。

具体做法:
可以在http://www.a.com/a.htmlhttp://script.a.com/b.html两个文件中分别加上document.domain = ‘a.com’;然后通过a.html文件中创建一个iframe,去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。当然这种办法只能解决主域相同而二级域名不同的情况代码如下:

www.a.com/a.html
document.domain="a.com";
var ifr=document.createElement('iframe');
//把b.html作为iframe载入了
ifr.src='http://script.a.com/b.html';
ifr.style.display='none';
document.body.appendChild(ifr);
ifr.onload=function(){
    var doc=ifr.contentDocument||ifr.contentWindow.document;
    //...
}

script.a.com上的b.html

document.domain='a.com';

解释

document.domain ,我们是设置一个页面的域名,某一个页面的域名默认为:window.location.hostname
即默认下:

document.domain===window.location.hostname //true

domain只能设置为主域名,不能设置为二级域名,或子域名。

再次强调这种方法的局限性

只能是二级或子级域名的任何页面相互通信,只需要设置它们的document.domain都为主域名。
设置的局限
例如:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.com、b.example.com 、example.com中的任意一个,但是不可以设成 c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。

安全性
安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。
操作
如果要一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。

iframe与Ajax

光设置了document.domain,在不同的子域名之间还是不能直接通过Ajax来访问的,但是我们可以通过iframe这个中间人来做到。
比如A.com使用脚本获取api.A.com的数据,那么我们可以用iframe来载入这个api.A.com的页面,由于我们修改了document.domain之后我们当前这个页面与iframe是可以通过js交互的,我们的js可以完全控制整个iframe,所以可以让iframe去发ajax请求然后收到的数据我们也可以获得了。

window.name跨域

原理

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
换言之
也就是说我们在一个窗口中开了N个iframe,N个iframe中的页面中的window都可以访问window.name属性。

这里我们有上个页面:

  • index.html
  • b.html
  • proxy.html

我们可以在index.html中设置一个iframe,它的src一开始是我们的b.html,然后我们通过iframe去访问这个b.html设置的window.name的值。等待加载完毕后我们又把这个src设置成同域下的proxy.html
这时我们才能访问iframe.contentWindow.name中的数据。
因为直接在index.html访问不同域名的b.html是无法访问到window的属性的。

另一个域名的 b.html

<script type="text/javascript">
    window.name = 'I was there!';    // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
                                     // 数据格式可以自定义,如json、字符串
</script>
一开始我直接让index.html访问b.html如下:

index.html的脚本

var URL= 'http://spw.linzhida.cc/b.html';

var ifr=document.createElement('iframe');
document.body.appendChild(ifr);
var loadfn=function(){
    var ifrwindow=ifr.contentWindow;
    var data=ifrwindow.name;//load data!
    console.log(data);
}
var removeWindow=function(ifr){
    ifr.contentWindow.document.write(' ');
    ifr.contentWindow.close();
    document.removeChild(ifr);
}

//添加事件给ifr!
//ifr.addEventListener('DOMContentLoaded', loadfn);
//给iframe应该添加的是onload! DOMContentLoaded不准确也没效果
ifr.onload=function(){
    loadfn();
}
// 
ifr.src=URL;

报错:
这里写图片描述

后来修改后的index.html的脚本
var URL= 'http://spw.linzhida.cc/b.html';
var sameOrigin='http://www.linzhida.cc/Proxy.html';
/**
 * 
必须是在ifr的src已经设置了之后才会能访问到window??
不是,只要创建了iframe并且添加到了当前页面之后就可以访问到了
or  document
 */
var ifr=document.createElement('iframe'),
    state=0;
document.body.appendChild(ifr);
var loadfn=function(){
    var ifrwindow=ifr.contentWindow;
    if(state==1){
        var data=ifrwindow.name;//load data!
        console.log(data);
    }else if(state==0){
        ifrwindow.location=sameOrigin; //设置代理文件
        state=1;
    }
}
var removeWindow=function(ifr){
    ifr.contentWindow.document.write(' ');
    ifr.contentWindow.close();
    document.removeChild(ifr);
}

//添加事件给ifr!
//ifr.addEventListener('DOMContentLoaded', loadfn);
//给iframe应该添加的是onload! DOMContentLoaded不准确也没效果
ifr.onload=function(){
    loadfn();
}

ifr.src=URL; //事先设置了第一次的src

proxy.html中无内容

总结起来即:iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。


HTML5新方法window.postMessage

跨文档消息传输! Cross Document Message
简称XDM,是指来自不同域的页面间传递消息。
主要两个目的地点

  • 对于XDM来说,一个是当前页面的iframe元素
  • 一个是当前页面弹出的窗口。
    这两个中的都可能是不同域的url,所以我们需要实现这种不同域的通信。

postMessage()方法接收两个参数:一个消息和一个表示消息接收方来自哪个域的字符串:

//
// Facebook已经使用了这个功能,
// 用postMessage支持基于web的实时消息传递。
/* 
otherWindow.postMessage(message, targetOrigin);
    otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
    message: 所要发送的数据,string类型。
    targetOrigin: 用于限制otherWindow,“*”表示不作限制
*/
var URL='http://sxw.linzhida.cc/b.html';
var ORIGIN='http://sxw.linzhida.cc',
        rootDiv=document.getElementById('root');
function createIframe(){
    var ifr=document.createElement('iframe');
    rootDiv.appendChild(ifr);
    ifr.src=URL;
    ifr.contentDocument.domain='linzhida.cc';
    return ifr;
}

var ifr=createIframe();
//iframe的src打开的页面需要和 你postMessage指定的ORIGIN是一个域名
window.onload=function(){
    //var targetOrigin=URL;  //URL.split('.');
    ifr.contentWindow.postMessage('i was here',ORIGIN);
}


错误

这里写图片描述
原因
这时因为我们的postMessage方法需要iframe中的window加载完才能接收到消息。
所以你必须将window改成ifr的onload事件

//变量提升,是对声明而言,如果你在声明之前进行RHS去取源值
//变量声明会被提升,但是变量赋值不会
var ifr=createIframe();
//iframe的src打开的页面需要和 你postMessage指定的ORIGIN是一个域名
ifr.onload=function(){
    //var targetOrigin=URL;  //URL.split('.');
    ifr.contentWindow.postMessage('i was here',ORIGIN);
}

接收端http://sxw.linzhida.cc/b.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>B page</title>
</head>
<body>
<script type="text/javascript">
    var URL='http://www.linzhida.cc';
    //document.domain = 'linzhida.cc';
    window.name="data is here!";
    window.addEventListener("message", receiveMessage, false);

    function receiveMessage(event){
        var origin=event.origin||event.originalEvent.origin;//For Chrome, the origin property is in the event.originalEvent object.
        if(origin!==URL){
            return;
        }
        //event.data   发送的数据
        //event.origin   发送端窗口的origin ,
        //event.source  reference of the window object that send the message
        console.log(event.data);
        console.log(event.source);
    }
</script>
</body>
</html>
注意:

第二个参数targetOrigin可以设置为*,这样就会把消息发送给来自任何域的文档,但不推荐。尽量提供一个准确的targetOrigin,如果你知道要发送的window的Document的URI,这样可以保证安全性。
上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。在讨论第二种方法的时候,我们说过,不同域的框架间是可以获取到对方的window对象的,而且也可以使用window.postMessage这个方法。

关于接受端的最佳实践

如果你要接受其他域or站点的数据,总是验证发送端的origin和source属性,不要监听来自任何域的message事件。 不然会造成一个跨站脚本漏洞。

优点:

  • 设置成特定的域,防止浏览器把消息发送到不安全的地方,保障安全通信。
  • 来源匹配才会把消息传递给内嵌的框架(iframe)中,否则postMessage什么也不做。 这一限制可以避免窗口中的位置在你不知情的情况下发生改变。
  • 包含iframe的页面可以确保自身不受恶意内容的侵扰,因为它只通过XDM和嵌入的框架通信。相同域的页面也可以用XDM

缺点:

  • event.source可能会被误认为是window对象。但是它实际只是window对象的一个代理,我们不能通过这个代理对象访问window对象的其他任何信息。
  • postMessage的第一个参数永远字符串。 后来这个参数定义又改了,可以传入任何数据结构,但是浏览器并非都实现了这个变化。最稳妥的做法还是先通过JSON.stringify()来包装这个数据。然后在onmessage事件中JSON.parse

兼容性:IE8+


iframe和location.hash

这个方法不细说了。 原理还是类似:
我们利用location.hash这个属性来传值,比如url:
http://a.com#hello 中的这个hello就是在a.com下访问location.hash的值,我们可以在a.com的index.html访问另一个域名如b.com的c.html,还是通过设置一个隐藏的iframe,将这个iframe的src设置成b.com/c.html, 然后这个hash值就可以作为参数传递用。
因为两个不同域的页面不能修改parent.location.hash的值

缺点:

1.数据直接暴露在url中,每次都是设置index.html的hash值来传递数据
2. 传递的数据的大小有限,因为浏览器对url的长度有限制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值