JavaScript两种跨域技术全面介绍

这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下的内容。

JavaScript这个安全策略在进行多iframe或多窗口编程、以及Ajax编程时显得尤为重要。根据这个策略,在baidu.com下的页面中包含的JavaScript代码,不能访问在google.com域名下的页面内容;甚至不同的子域名之间的页面也不能通过JavaScript代码互相访问。对于Ajax的影响在于,通过XMLHttpRequest实现的Ajax请求,不能向不同的域提交请求,例如,在abc.example.com下的页面,不能向def.example.com提交Ajax请求,等等。

然而,当进行一些比较深入的前端编程的时候,不可避免地需要进行跨域操作,这时候“同源策略”就显得过于苛刻。本文就这个问题,概括了跨域所需要的一些技术。

下面我们分两种情况讨论跨域技术:首先讨论不同子域的跨域技术,然后讨论完全不同域的跨域技术。

(一)不同子域的跨域技术。

我们分两个问题来分别讨论:第一个问题是如何跨不同子域进行JavaScript调用;第二个问题是如何向不同子域提交Ajax请求。

先来解决第一个问题,假设example.com域下有两个不同子域:abc.example.com和def.example.com。现在假设在def.example.com下面有一个页面,里面定义了一个JavaScript函数:


代码如下:

function funcInDef() {
    .....
}

我们想在abc.example.com下的某个页面里调用上面的函数。再假设我们要讨论的abc.example.com下面的这个页面是以iframe形式嵌入在def.example.com下面那个页面里的,这样我们可能试图在iframe里做如下调用:

代码如下:

window.top.funcInDef();

好,我们注意到,这个调用是被前面讲到的“同源策略”所禁止的,JavaScript引擎会直接抛出一个异常。
为了实现上述调用,我们可以通过修改两个页面的domain属性的方法做到。例如,我们可以将上面在abc.example.com和def.example.com下的两个页面的顶端都加上如下的JavaScript代码片段:


代码如下:

document.domain = "example.com";

这样,两个页面就变为同域了,前面的调用也可以正常执行了。

这里需要注意的一点是,一个页面的document.domain属性只能设置成一个更顶级的域名(除了一级域名),但不能设置成比当前域名更深层的子域名。例如,abc.example.com的页面只能将它的domain设置成example.com,不能设置成sub.abc.example.com,当然也不能设置成一级域名com。

上面的例子讨论的是两个页面属于iframe嵌套关系的情况,当两个页面是打开与被打开的关系时,原理也完全一样。

下面我们来解决第二个问题:如何向不同子域提交Ajax请求。
通常情况下,我们会用与下面类似的代码来创建一个XMLHttpRequest对象:


代码如下:

factories = [function() {
    return new XMLHttpRequest();
},
function() {
    return new ActiveXObject("Msxml2.XMLHTTP");
},
function() {
    return new ActiveXObject("Microsoft.XMLHTTP");
}];
function newRequest() {
    for (var i = 0; i & lt; factories.length; i++) {
        try {
            var factory = factories[i];
            return factory();
        } catch(e) {}
    }
    return null;
}

上面的代码中引用ActiveXObject,是为了兼容IE6系列浏览器。每次我们调用newRequest函数,就获得了一个刚刚创建的Ajax对象,然后用这个Ajax对象来发送HTTP请求。例如,下面的代码向abc.example.com发送了一个GET请求:


代码如下:

var request = newRequest();
request.open("GET", "http://abc.example.com" );
request.send(null);

假设上面的代码包含在一个abc.example.com域名下的页面里,则这个GET请求可以正常发送成功,没有任何问题。然而,如果现在要向def.example.com发送请求,则出现跨域问题,JavaScript引擎抛出异常。
解决的办法是,在def.example.com域下放置一个跨域文件,假设叫crossdomain.html;然后将前面的newRequest函数的定义移到这个跨域文件中;最后像之前修改document.domain值的做法一样,在crossdomain.html文件和abc.example.com域下调用Ajax的页面顶端,都加上:


代码如下:

document.domain = "example.com";

为了使用跨域文件,我们在abc.example.com域下调用Ajax的页面中嵌入一个隐藏的指向跨域文件的iframe,例如:


代码如下:

<iframe name="xd_iframe" style="display:none" src="http://def.example.com/crossdomain.html">
</iframe>

这时abc.example.com域下的页面和跨域文件crossdomain.html都在同一个域(example.com)下,我们可以在abc.example.com域下的页面中去调用crossdomain.html中的newRequest函数:


代码如下:

var request = window.frames["xd_iframe"].newRequest();

这样获得的request对象,就可以向http://def.example.com发送HTTP请求了。

(二)完全不同域的跨域技术。

如果顶级域名都不相同,例如example1.com和example2.com之间想通过JavaScript在前端通信,则所需要的技术更复杂些。

在讲解不同域的跨域技术之前,我们首先明确一点,下面要讲的技术也同样适用于前面跨不同子域的情况,因为跨不同子域只是跨域问题的一个特例。当然,在恰当的情况下使用恰当的技术,能够保证更优的效率和更高的稳定性。

简言之,根据不同的跨域需求,跨域技术可以归为下面几类:
1、JSONP跨域GET请求
2、通过iframe实现跨域
3、flash跨域HTTP请求
4、window.postMessage
下面详细介绍各种技术。
1. JSONP。
利用在页面中创建<script>节点的方法向不同域提交HTTP请求的方法称为JSONP,这项技术可以解决跨域提交Ajax请求的问题。JSONP的工作原理如下所述:
假设在http://example1.com/index.php这个页面中向http://example2.com/getinfo.php提交GET请求,我们可以将下面的JavaScript代码放在http://example1.com/index.php这个页面中来实现:


代码如下:

var eleScript= document.createElement("script");
eleScript.type = "text/javascript";
eleScript.src = "http://example2.com/getinfo.php";
document.getElementsByTagName("HEAD")[0].appendChild(eleScript);

当GET请求从http://example2.com/getinfo.php返回时,可以返回一段JavaScript代码,这段代码会自动执行,可以用来负责调用http://example1.com/index.php页面中的一个callback函数。

JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。

JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

2. 通过iframe实现跨域。

iframe跨域的方式,功能强于JSONP,它不仅能用来跨域完成HTTP请求,还能在前端跨域实现JavaScript调用。因此,完全不同域的跨域问题,通常采用iframe的方式来解决。

与JSONP技术通过创建<script>节点向不同的域提交GET请求的工作方式类似,我们也可以通过在http://example1.com/index.php页面中创建指向http://example2.com/getinfo.php的iframe节点跨域提交GET请求。然而,请求返回的结果无法回调http://example1.com/index.php页面中的callback函数,因为受到“同源策略”的影响。

为了解决这个问题,我们需要在example1.com下放置一个跨域文件,比如路径是http://example1.com/crossdomain.html。

当http://example2.com/getinfo.php这个请求返回结果的时候,它大体上有两个选择。
第一个选择是,它可以在iframe中做一个302跳转,跳转到跨域文件http://example1.com/crossdomain.html,同时将返回结果经过URL编码之后作为参数缀在跨域文件URL后面,例如http://example1.com/crossdomain.html?result=<URL-Encoding-Content>。

另一个选择是,它可以在返回的页面中再嵌入一个iframe,指向跨域文件,同时也是将返回结果经过URL编码之后作为参数缀在跨域文件URL后面。

在跨域文件中,包含一段JavaScript代码,这段代码完成的功能,是从URL中提取结果参数,经过一定处理后调用原来的http://example1.com/index.php页面中的一个预先约定好的callback函数,同时将结果参数传给这个函数。http://example1.com/index.php页面和跨域文件是在同一个域下的,因此这个函数调用可以通过。跨域文件所在iframe和原来的http://example1.com/index.php页面的关系,在前述第一种选择下,后者是前者的父窗口,在第二种选择下,后者是前者的父窗口的父窗口。

根据前面的叙述,有了跨域文件之后,我们就可以实现通过iframe方式在不同域之间进行JavaScript调用。这个调用过程可以完全跟HTTP请求无关,例如有些站点可以支持动态地调整在页面中嵌入的第三方iframe的高度,这其实是通过在第三方iframe里面检测自己页面的高度变化,然后通过跨域方式的函数调用将这个变化告知父窗口来完成的。

既然利用iframe可以实现跨域JavaScript调用,那么跨域提交POST请求等其它类型的HTTP请求就不是难事。例如我们可以跨域调用目标域的JavaScript代码在目标域下提交Ajax请求(GET/POST/etc.),然后将返回的结果再跨域传原来的域。

使用iframe跨域,优点是功能强大,支持各种浏览器,几乎可以完成任何跨域想做的事情;缺点是实现复杂,要处理很多浏览器兼容问题,并且传输的数据不宜过大,过大了可能会超过浏览器对URL长度的限制,要考虑对数据进行分段传输等。

3. 利用flash实现跨域HTTP请求

据称,flash在浏览器中的普及率高达90%以上。

flash代码和JavaScript代码之间可以互相调用,并且flash的“安全沙箱”机制与JavaScript的安全机制并不尽相同,因此,我们可以利用flash来实现跨域提交HTTP请求(支持GET/POST等)。
例如,我们用浏览器访问http://example1.com/index.php这个页面,在这个页面中引用了http://example2.com/flash.swf这个flash文件,然后在flash代码中向http://example3.com/webservice.php发送HTTP请求。

这个请求能否被成功发送,取决于在example3.com的根路径下是否放置了一个crossdomain.xml以及这个crossdomain.xml的配置如何。flash的“安全沙箱”会保证:仅当example3.com服务器在根路径下确实放置了crossdomain.xml文件并且在这个文件中配置了允许接受来自example2.com的flash的请求时,这个请求才能真正成功。下面是一个crossdomain.xml文件内容的例子:

代码如下:

<?xml version="1.0"?>
<cross-domain-policy>
    <allow-access-from domain="example2.com" />
</cross-domain-policy>

4. window.postMessage
  window.postMessage是HTML标准的下一个版本HTML5支持的一个新特性。受当前互联网技术突飞猛进的影响,浏览器跨域通信的需求越来越强烈,HTML标准终于把跨域通信考虑进去了。但目前HTML5仍然只是一个draft。
  window.postMessage是一个安全的实现直接跨域通信的方法。但是目前并不是所有浏览器都能支持,只有Firefox 3、Safari 4和IE8可以支持这个调用。

使用它向其它窗口发送消息的调用方式大概如下:


代码如下:

otherWindow.postMessage(message, targetOrigin);

在接收的窗口,需要设置一个事件处理函数来接收发过来的消息:


代码如下:

window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
    if (event.origin !== "http://example.org:8080") return;
}

消息包含三个属性:data、origin(携带发送窗口所在域的真实信息)和source(代表发送窗口的handle)。

安全性考虑:使用window.postMessage,必需要使用消息的origin和source属性来验证发送者的身份,否则会造成XSS漏洞。

window.postMessage在功能上同iframe实现的跨域功能同样强大,并且使用简单,效率更高,但缺点是它目前在浏览器兼容方面有待提高。

需要对原文补充的是,在IE6,IE7下可利用IE的Opener可赋值为Object或Function的漏洞,提供postMessage方案的补充方案:
主页面:

代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>CrossDomain</title>
</head>
<body>
    <iframe src="http://sh-tanzhenlin/CrossDomain-child.html"
        frameborder="0" visible="false" height="0" width="0" id="ifrChild"></iframe>

<script type="text/javascript">
        var child = document.getElementById("ifrChild");
        var openerObject = {
                funcInParent:function(arg){
                    alert(arg);
                    alert('executed by a function in parent page');
                }
            }

if(!+'\v1' && !'1'[0]){ //test browser is ie6 or ie7  
            //crack
            child.contentWindow.opener = openerObject;
        }
        else{
            //postMessage showtime
        }

function onClick(){
            //debugger;
            openerObject.funcInIframe('data from parent page ');
        }
    </script>
    <input type="button" value="click me" onclick="onClick()" />
</body>
</html>

用iframe内嵌其它域下的页面:

代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
               <script type="text/javascript">
                        onload = function(){
                                if(!+'\v1' && !'1'[0]){ // test browser if is ie6 or ie7
                                        window.opener.funcInIframe=function(arg){
                                                alert(arg);
                                                alert('executed by a function in iframe');
                                        }
                                        window.opener.funcInParent('data from iframe')
                                }
                        }
                </script>
        </head>
        <body>
        </body>
</html>

注:postMessage方式正以意想不到的速度得到各种新浏览器的支持,应予以着重考虑。

(0)

相关推荐

  • JavaScript跨域方法汇总

    做Web开发经常需要面对跨域问题,跨域问题的根源是浏览器安全中的同源策略,比如说,对于http://www.a.com/1.html来说: 1.http://www.a.com/2.html是同源的: 2.https://www.a.com/2.html是不同源的,原因是协议不同: 3.http://www.a.com:8080/2.html是不同源的,原因是端口不同: 4.http://sub.a.com/2.html是不同源的,原因是主机不同. 在浏览器中,<script>.<img

  • javascript跨域原因以及解决方案分享

    产生跨域问题的原因 跨域问题是浏览器同源策略限制,当前域名的js只能读取同域下的窗口属性. 跨域问题产生的场景 当要在在页面中使用js获取其他网站的数据时,就会产生跨域问题,比如在网站中使用ajax请求其他网站的天气.快递或者其他数据接口时以及hybrid app中请求数据,浏览器就会提示以下错误.这种场景下就要解决js的跨域问题. XMLHttpRequest cannot load http://你请求的域名. No 'Access-Control-Allow-Origin' header

  • Javascript 跨域访问解决方案

    这里分两类情况:一.基于同一父域的子域之间页面的访问:参见如下3个domain域:taobao.com.jipiao.taobao.com.promotion.taobao.com:它们有相同的父域taobao.com.二.基于不同父域页面之间的访问:参见如下3个domain域:taobao.com.baidu.com.sina.com.cn:它们具有不同的父域. 解决它们之间跨域的方案有:方案1:服务器Proxy域A的页面JS需要访问域B下的链接获取数据,该方案在域A的服务器端建立一个Prox

  • javascript跨域刷新实现代码

    三个页在同一个窗口,分别为main.htm,left.htm和right.htm. main.htm 复制代码 代码如下: <html> <head> <title>实现跨</title> </head> <body> <div>主窗口</div> <iframe id="left" name="left" width="500px" heigh

  • 关于JavaScript跨域问题及实时刷新解决方案

    在自己页面显示其他网站上面的数据,需要用Ajax,就涉及到跨域问题, 解决方案:jQuery.support.cors = true; (浏览器支持跨域访问), 实例: 复制代码 代码如下: //浏览器支持跨域访问 jQuery.support.cors = true; $.ajax({ url: "http://www.rj99999.com/Price/ListPrice.html", dataType: 'html', success: function (data, textS

  • javascript使用window.name解决跨域问题第1/2页

    window.name 传输技术,原本是 Thomas Frank 用于解决 cookie 的一些劣势(每个域名 4 x 20 Kb 的限制.数据只能是字符串.设置和获取 cookie 语法的复杂等等)而发明的(详细见原文:<Session variables without cookies>),后来 Kris Zyp 在此方法的基础上强化了 window.name 传输 ,并引入到了 Dojo(dojox.io.windowName),用来解决跨域数据传输问题.window.name 传输技

  • 使用JavaScript 实现各种跨域的方法

    一.一些概念 ①传统Ajax:交互的数据格式--自定义字符串或XML描述: 跨域--通过服务器端代理解决. ②如今最优方案:使用JSON格式来传输数据,使用JSONP来跨域. ③JSON:一种数据交换格式.基于纯文本.被原生JS支持.     格式:两种数据类型描述符:大括号{ }.方括号[ ].分隔符逗号.映射符冒号.定义符双引好. ④JSONP:一种跨域数据交互协议,非官方. 1.Web页面调用js文件,可跨域.扩展:但凡有src属性的标签都具有跨域能力. 2.跨域服务器 动态生成数据 并存

  • JavaScript中跨域调用Flash的方法

    要做一个页面上短信息的提示音的功能,本来想用HTML5中Audio+IE下的bgsound来实现,可是发现每种浏览器对Audio的解码类型还不一样,顿时有种崩溃的感觉.没办法还是用Flash稳妥一点吧. 相信JavaScript与Flash交互大家都会有所接触或者有所耳闻.其实我也是第一次整这个玩意.具体的方法就不说了,很多资料. 开始的时候功能都做得差不多了,实现和没问题.可是就是到了最后,将swf文件放到资源服务上后再调用时出来问题,我就想肯定又是让人蛋疼的跨域问题(CrossDomain)

  • AJAX javascript的跨域访问执行

    突然感觉就是这里的问题,研究一下,搞定后其实觉得挺容易的,只是自己知识还是有些欠缺,解决方法如下: 阻塞的AJAX请求 我们先来证实一下请求的阻塞情况吧.我们使用如下的代码: 连续发起三个请求  复制代码 代码如下: function simpleRequest()  {     var request = new XMLHttpRequest();      request.open("POST", "Script.ashx");      request.sen

  • JavaScript使用HTML5的window.postMessage实现跨域通信例子

    JavaScript由于同源策略的限制,跨域通信一直是棘手的问题.当然解决方案也有很多: 1.document.domain+iframe的设置,应用于主域相同而子域不同: 2.利用iframe和location.hash,数据直接暴露在了url中,数据容量和类型都有限 3.Flash LocalConnection, 对象可在一个 SWF 文件中或多个 SWF 文件间进行通信, 只要 在同一客户端就行,跨应用程序, 可以跨域. window.name 保存数据以及跨域 iframe 静态代理动

  • Javascript跨域请求的4种解决方式

    什么情况下才会出现跨域? 假设域名是:http://www.example.com.cn/ 如果所请求的域名跟这个域名不致,这种情况就是跨域,由于跨域存在漏洞,所以一般来说正常的跨域请求方式是请求不到的. 解决方式: 一.window.name 1. 服务器返回 复制代码 代码如下: <script>window.name='{"id":"3", "name":"leisure"}';</script>

  • javascript跨域的4种方法和原理详解

    下表给出了相对http://store.company.com/dir/page.html同源检测的结果: 要解决跨域的问题,我们可以使用以下几种方法: 一.通过jsonp跨域 在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的.但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的. 比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是http://example.com

  • JavaScript实现的双向跨域插件分享

    由于浏览器(同源策略)限制,JavaScript 跨域的问题,一直是一个颇为棘手的问题.HTML5 提供了跨文档消息传输的功能,在网页文档之间互相接收与发送信息.使用这个功能,不仅同源(域 + 端口号)的 Web 网页之间可以互相通信,还可以在两个不同域名之间实现跨域通信. 跨文档消息传输Cross Document Messaging提供了postMessage方法在不同网页文档之间互相传递数据,支持实时消息传递.现在很多浏览器都将支持这个功能,比如Google Chrome 2.0+.Int

随机推荐