`
bufanliu
  • 浏览: 200182 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

实现不同域(Domain)之间的数据交换

阅读更多
引言
前些天在对公司原有的 web 应用进行改版时遇到一个问题,当时需要从原有的应用中提取出一部分,用一个更为通用的来进行替换,并且仍然保留原有的应用接口。原有的应用属于 news.mycompany.com 域,而新应用将被部署到 upload.mycopany.com。当我试着从新的域向 news.mycompany.com 传递数据时,在前台遇到了浏览器返回的“拒绝访问(Access Denied)” 的错误信息,通过参考在 google 中查到的大量英文资料找到了问题的症结,并通过指定两个域中页面的 docment.domain 属性使问题得到了部分解决。后来一时兴起在 google 中查找与 document.domain 相关的中文资料,但得到的大部分是网络安全方面的文章,很少有文章提及通过指定页面的 document.domain 属性能够实现两个域之间的数据交换,于是决定写下此文,希望能够做到抛砖引玉吧。

关键字: JavaScript, Domain, Access Denied

问题的提出
在开发 Web 应用时经常会遇到需要在两个帧之间传递数据的情况,这里的帧可以是 frameset 中的 frame 也可以是独立的窗口。常见的情况是一个帧作为应用的主体,另一个帧则提供一些供用户选择的选项,用户选择完毕后,该帧把用户作出的选择发送到服务器并向主要的帧传递一些信息,这里的信息可能是用户的选择也可能是服务器返回的数据。当两个帧中的内容同属于一个域时实现以来比较简单,但是当它们分属于不同域时问题就变得复杂而棘手了,因为这里涉及到了数据访问的安全性问题,搞不好就会遇到浏览器返回的“拒绝访问(Access Denied)” 的错误信息。

可能的解决方案
下面我们将通过几个试验来分析一下在分属于不同域的帧之间传递数据的一些方法。
利用客户端脚本(如 JavaScript)和窗口句柄在两个帧之间传递数据
利用 MSIE 提供的对话框在两个帧之间传递数据
利用服务器端的应用,通过 session 来传递数据
方案一

用客户端脚本实现两个帧之间的数据交换应该是最为轻量级的方式之一了,这样做不会增加服务器的负载也不会占用网络带宽,数据交换完全是在客户端完成。下面就让我们先来了解一下用客户端脚本(以 JavaScript 为例)和窗口句柄如何实现一个域内的数据交换。

我们通过一个实例来进行说明:假设需要给用户提供一个新闻的录入界面,用户可以用它录入新闻的原始内容,并且可以在其中嵌入一副图片。为了实现这个功能界面我们设计了两个帧,或者说是两个窗口:

主窗口: 新闻内容的主要编辑界面,用户可以在里面录入新闻的标题、作者、新闻主体等内容,还有一个图片框可以预览上传的图片

弹出窗口: 处理图片上传的界面,用户可以选择本地图片进行上传,成功后它把服务器上文件的 url 返回给主窗口进行预览

为了简单起见,我们假设两个窗口中的内容都是静态的,主窗口对应的文件为 NewsEdit.html,弹出窗口对应的文件为 ImgUpload.html(而大多数情况下两个窗口的内容都应该是动态生成的)。

其中 NewsEdit.html 位于 news.mycompany.com 的主目录下,其源代码如下所示:
<!-- File: NewsEdit.html (http://news.mycompany.com/NewsEdit.html) -->
<html>
<head>
<title>The Content Editing Interface</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<script language="JavaScript">
<!--
  /* OpenWin 用来在一个弹出窗口中显示 ImgUpload.html 的内容*/
  function OpenWin(){//Open window
    url='http://news.mycompany.com/upload/ImgUpload.html';
    newwindow = window.open(url,"ImgUpload","height=135,width=300");
    if (!newwindow.opener) newwindow.opener=self;
  }
-->
</script>
</head>

<body>
<h2>Edit your content here</h2>
<!-- 调用后台应用 newsedit 来保存新闻内容 -->
<form action="http://news.mycompany.com/newsedit" method="post" name="addnews">
  <!-- 新闻标题 -->
  Title:<input type="text" name="title"><br>
  <!-- 新闻作者 -->
  Author:<input type="text" name="author"><br>
  The content <br>
  <!-- 新闻内容 -->
  <textarea name="contentBody" cols="100" rows="10"></textarea>
  <br>
  <!-- 点击连接打开上传图片的小窗口 -->
  <a href="JavaScript:OpenWin()">Upload Image File</a>
  <br>
  <!-- UserImg 用来预览上传成功后的图片文件 -->
  <img name="UserImg" style="width: 100px; height: 100px;" src="" border="1">
  <br><br>
  <input type="submit" name="SaveContent" value="Submit">
  <input type="reset" name="ClearContent" value="Reset">
</form>
</html>


ImgUpload.html 位于 news.mycompany.com 的 upload 子目录下,其源代码如下所示: <!-- File: ImgUpload.html (http://news.mycompany.com/upload/ImgUpload.html) -->
<html>
<head>
<title>Imgage Upload Interface</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>

<body>
<h2>Image Upload</h2>
<!-- 调用后台应用来处理上传的图片 -->
<form action="http://news.mycompany.com/upload/imgupload" method="post" enctype="multipart/form-data" name="upload">
  <!-- 由用户选择本地文件 -->
  <input type="file" name="imgfile">
  <input type="submit" name="Submit" value="Upload">
</form>
</html>


另外介绍一下我们的两个后台应用:
newsedit: 位于 news.mycompany.com 的主目录下,接受用户的 POST 请求,将编辑界面的新闻元素存储到后台数据库

imgupload:  位于 news.mycompany.com 的 upload 子目录下,接受用户的 POST 请求,将本地的图片文件上传到服务器,并返回图片文件完整的 url。

下面是 imgupload 处理完 POST 请求后返回的页面内容,该内容显示在 ImgUpload.html 所占据的弹出窗口中:
<html>
<head>
<title>File Upload Successfully</title>
</head>
<body>
<h3>File Uploaded Successfully!</h3>
<script language="JavaScript">
  <!-- 获取主窗口的句柄 -->
  parwin=self.opener;
  <!-- 获取对 img 元素的引用,并用上传文件的 url 为 img 元素的 src 属性赋值,这样在客户端就可以预览了 -->
  <!-- 为了简化问题,我们将对 img 元素的引用直接写在程序中 -->
  parwin.addnews.UserImg.src="http://news.mycompany.com/img/2003_07/06/1057478464859.gif";
</script>
</body>
</html>


返回的页面除了显示成功信息外,还利用脚本向主窗口传递了上传文件的 url,具体过程是:
首先通过 "self.opener" 获得主窗口(即 NewsEdit.html 所在窗口)的句柄;然后用上传文件的 url 对主窗口中 UserImg 元素的 src 属性进行赋值,这样在主窗口中就可以看到上传后的图片了。

好了,我们的第一个实验已经成功了,实验结果告诉我们:当两个帧中的内容同属于一个域时,利用客户端脚本和窗口句柄在其中传递数据是没有问题的。接下来我们把 ImgUpload.html 和 imgupload 从 news.mycompany.com 提取出来,部署到 img.mycompany.com 的对应目录下,并修改 NewsEdit.html 中引用 ImgUpload.html 时的 url。这样当我们试着用 JavaScript 从 img.yourcompanu.com 向 bbs.yourcompany.com 传递数据时,浏览器就会弹出“拒绝访问(Access Denied)” 的错误框,表明我们违反了他的安全策略,并且数据无法正常传递过来。

其实,你可以直接把实验一中 imgupload 返回的内容另存为一个文件并部署到 img.mycompany.com,在 NewsEdit.html 中调用 window.open() 方法直接引用这个文件就可以进行测试了。

我们之所以会遇到“拒绝访问(Access Denied)” 的的错误信息,其原因在于:

最初,浏览器的开发商、开发团体出于安全性的考虑,默认情况下是不允许在分属于不同域的页面之间进行数据交换和方法调用的,当遇到这种情况时浏览器就会返回“拒绝访问(Access Denied)”的错误。

“那为什么即使我的两个页面属于同一个域我还是会遇到‘拒绝访问’的错误呀?”
如果是这种情况,那就要看你的弹出窗口中的内容是否始终属于同一个域,看一下你的 ImgUpload.html 是不是调用了属于其他域的应用,并且该应用在窗口中重新写入了内容,如果是这样那你的弹出窗口就变质了,它最后属于另外一个域,你当然会遇到“拒绝访问”的错误。

“这么说如果两个页面分属于不同域的话我们就无法在两个窗口之间传递数据了吗?”
事实基本上是这样的--一个令人沮丧的消息。
但答案也并非绝的--好像还有希望。

是的,一些浏览器的开发商、开发团体在开发高版本的浏览器时对原有策略进行了部分调整,这些调整给我们带来了一线生机:
当两个页面在进行数据交换时,浏览器会首先比较两个页面的 domain 属性,如果 domain 属性相同,那么浏览器就允许它们之间的数据交换,否则就返回“拒绝访问(Access Denied)”的错误。

“那么我们如何才能蒙蔽浏览器,让它认为两个页面的 domain 属性相同呢?”
这就要靠脚本来实现了,在 JavaScript 中我们可以通过在页面中加入如下声明来强制指定页面所属的域。

<script language="JavaScript">
<!--
document.domain = "mycompany.com"; //指定 document 所属的域
-->
</script>

加入上面的声明就可以蒙蔽浏览器,在原本属于两个不同域的页面之间进行数据交换了。但需要注意:只有把上面的声明加入到需要进行数据交换的所有文件中才会有效,只在一个域的文件中加入上面的声明是不起作用的。另外,声明部分最好能插入到页面的 <head></head> 标记中间,这一点也是用脚本进行开发时所被提倡的。有关 JavaScript 中的 document 和 domain 等可以参考 http://www.werelight.com/docs/JavaScript_Quick_Reference.htm


“使用这种方法有什么限制码?”
因该说用这种方法来实现不同域之间的数据传递还是有很多的限制的,主要表现为以下两点:

document.domain 属性是不可以随便设置的,它只能被设置为文件所属域的上级域。如假设 ImgUpload.html 属于 img.mycompany.com ,那么它的 document.domain 属性可以设置为“mycompany.com” ,但不能设置为“img.mycompany” 或其他的,如“foo.com”。
只有当两个域存在相同的上级域时,才能通过指定 document.domain 来实现它们之间的数据交换,并且 document.domain 属性必须被 设置为二者的公共域。例如,假设 NewsEdit.html 属于 news.mycompany.com,而 ImgUpload.html 属于 img.yourcompany.com,那么无论你如何设置 document.domain 都无法在它们之间交换数据;再比如,假设 ImgUpload.html 属于 img.mycompany.com,那么我们可以把两个页面的 document.domain 属性设置为“mycompany.com”,但不可以设置为“img.mycompany” 或其他的什么域,如“foo.com”。
并非所有的浏览器都支持对 document.domain 属性进行设置。如 MSIE 和 Netscape 它们4.0以前的版本是不支持对该属性的设置的;另外有趣的是虽然 Netscape 在4.0以后开始支持对 domain 进行设置了,但在4.03 和4.04两个版本中 Netscapre 居然又把上面的功能给取消了。
利用其他脚本,如 vbscript 或 jscript 实现这种跨域的数据交换其原理与用 JavaScript 是一样的,大家可以参考相关资料来实现。

方案二
下面我们来看一下利用 MSIE 提供的对话框能不能解决两个域之间的数据交换问题
首先我来简单介绍一下 MSIE 对话框:MSIE 提供的 showModalDialog 和 showModelessDialog 方法可以用来在一个单独的帧中显示一个模态或非模态对话框,两个方法都通过一个 URI 参数来指定对话框帧中的内容;可选的参数 vArguments 用来向对话框帧传递任何类型(包括数组类型)的参数;另外还有一个可选的参数 sFeatures 是用来定义对话框帧的显示效果,如位置、字体等等的;

注意 Netscape Navigator 、Mozilla 和 Opera 浏览器是没有与之对应的方法的,也就是说除了 MSIE 之外其他几大浏览器都不支持用 showModalDialog 或 showModelessDialog 显示对话框,如果你感兴趣的话这里有一篇文章能够教你如何通过其他方式来模拟一个模态对话框,详见 Simulating Modal Dialog Windows

“Because a modal dialog box can include a URL to a resource in a different domain, do not pass information through the vArguments parameter that the user might consider private. The vArguments parameter can be referenced within the modal dialog box using the dialogArguments property of the window object.”--引自 MSDN showModalDialog
上面一段话说明:通过 sURL 参数我们可以将另一个域的资源用为话框的内容,但这种情况下我们就不能再向对话框传递任何参数了,只有当所引用的资源与引用它的页面属于同一个域时,我们才可以利用 window.dialogArguments 获得从引用页中传递过来的参数。

“那么我能不能像方案一中那样通过强制指定两个页面中的 document.domain 属性来蒙蔽浏览器,使其认为两个页面属于同一个域呢?”
确实有人提出过这种想法,笔者也试着这样做过,但最后还以失败而告终:在两个页面中强制指定 document.domain 了属性后,无论两个页面是否属于同一个域,对话框都无法正常识别从主页面传递过来的参数。

在此次实验中我使用了3个文件

main.html : 部署在 a.mycompany.com,通过调用 showModalDialog 引用另外两个文件
localdialog.html  : 与 main.html 一起部署在 a.mycompany.com
remotedialog.html : 部署在 b.mycompany.com,其内容与 localdialog.html 完全一样

main.html 在调用用 showModalDialog 方法时,通过 vArguments 向对话框传递了参数:"Can you hear me?",希望对话框能够接收到这个参数;如果对话框接收到了,那么它将调用 window.alert() 方法打印出这条消息,然后向main.html 返回一个结果:"Yes I do, I hear you from " + document.domain ;如果main.html 接收到了对话框返回的结果,那么它同样会调用 window.alert() 打印出结果的内容。

其中 main.html 的源代码如下所示: <html>
<head>
<title>show modal dialog</title>
<script>
<!--
//document.domain  = "mycompany.com";

<!-- 打开一个模态对话框,显示 url 所代表的资源 -->
function openDialog(url) {
    <!-- 向对话框传递参数 -->
    var args = new Object();
    args.content = "Can you hear me?";
    var rv = window.showModalDialog(url, args);
    <!-- 显示对话框所返回的结果 -->
    if (rv) {
        alert("dialog returns :" + rv);
    } else {
        alert("dialog returns nothing");
    }
}
-->
</script>
</head>

<body>
<!-- 引用 b.mycompany.com 中的资源 -->
<a href="#" onclick="openDialog('http://b.mycompany.com/remotedialog.html');return false;">
  I will Open a remote dialog from news.soufun.com
</a>
<br>
<!-- 引用本地的资源 -->
<a href="#" onclick="openDialog('./invokebyhouse.html');return false;">
  I will Open a local dialog
</a>
</body>
</html>


localdialog.html(remotedialog.html) 的源代码如下所示: <html>
<head>
<title>a remote dialog</title>
<script>
<!--
//document.domain  = "mycompany.com";

onload = function() {
  var args = window.dialogArguments;
  alert("You send me: " + args.content);
  btnCan.onclick = function() {
    window.returnValue = "Yes I do, I hear you from " + document.domain;
    close();
  }
}
-->
</script>
</head>

<body>
Im here, Im a dialog <br>
I will return something to the main window<br>
<input id="btnCan" type="button" style="text-align:center;" value="Close">
</body>
</html>


通过实验发现:

main.html 总是能正常的接收从对话框中返回的结果,无论对话框是位于a.mycompany.com 还是 b.mycompany.com,也无论是否设置了 document.domain 属性;
在没有设置 document.domain 属性时,localdialog.html 可以正常接收从 main.html 传递过来的参数,但如果设置了 document.domain 属性, localdialog.html 读取到的参数就变成 null 了。
而无论是否设置了 document.domain 属性,在 remotedialog.html 读取从 main.html 传递过来的参数得到的始终都是 null。
非常遗憾,实验结果告诉我们:用对话框是无法实现这种跨域的数据交换的。

如果我的实验中存在某些漏洞,或者在你的实验中对话框读取到了从 main.html 传递过来的参数,有劳你通过 Email告知我,谢谢!

注(2004-12-28):您在进行测试的时候可能会得到不同的测试结果,因为随着IE 的更新或者补丁的作用,这种跨域的数据交换行为可能会被调整。例如笔者在加入这段注解的时候上面的第1条就已经不再成立了——main.html 并不是总能接收到从隶属于b.mycompany.com 的对话框返回的数据,只有两者都把document.domain 属性设置为mycompany.com 后main.html 才能接收到对话框返回的结果。见相关讨论:about showModalDialog
"Even though there is no mention in the documentation of passing arguments or returning a value being blocked in the cross-domain case, I think that may actually have changed as the consequence of one of the *many* IE upgrades and/or security patches."


方案三
应该说利用服务器端应用实现这种跨域的数据交换是最为可靠的方式了,因为这几乎不会受到客户端的限制,不像前面两种方式:有的客户端不支持 document.domain 属性,有的不支持对话框等等。那么是不是说用服务器端应用解决不同域之间的数据交换是应该最优先考虑的方案呢?答案是否定的,因为服务器端应用也有它的致命伤:即接收数据的一方不能实时的显示从对方传递过来的数据,它只有在处理了 GET 或 POST 请求后才能使数据得到展现,在这个过程中如果未做任何特殊处理,那么用户在该帧中编辑的内容将被清除掉,这往往是我们所不希望看到的。有关用服务器端应用实现跨域的数据交换我们就不再举例子了,如果您有兴趣的话可以到 google 上查一下相关的资料。

总结
在上面介绍的三种方案中,除方案二尚不能实现在分属于不同域的帧之间进行数据交换之外,经证明方案一和方案三都是可行的,不过这两种方案又各有利弊:

方案一的优点在于:用客户端脚本和窗口句柄不必占用服务器资源和网络带宽,可以做到数据的实时展现,并且不会影响到对方帧中的已有内容,其缺点是应用范围较小,要受到客户端浏览器的限制;
方案三由于是利用的服务器端应用,所以几乎不会受到客户端的影响,其缺点是无法做到数据的实时展现,有时候还要采取某种措施来维持对方帧中的已有内容。
针对这两种可行方案,大家在应用时应该灵活选择,如果你比较看重数据的实时展现那么就可以考虑采用前者;而如果应用的平台无关性是你衡量应用的标准那就应该考虑采用后者。

另外,如果您有其他可选方案的话,非常感谢您能通过 Email 告知我,以填补我在这面方面的空白,谢谢!

编后语:
非常感谢您能够看到这里,对于因为本文的晦涩难懂而给您带来的对自己理解能力的怀疑我深表歉意:),希望以后能做的更好!
分享到:
评论

相关推荐

    如何实现AD域账户导入导出.pdf

    AD域(Active Directory Domain)是微软的目录服务,它管理和存储用户、设备和其他资源的信息,主要用在Windows网络环境中。导入导出AD域账户信息对于日常的IT管理来说是一个非常重要的任务,它涉及到账号的批量创建...

    博科光纤交换机修改DOMAIN ID

    在两个或多个博科FC交换机之间建立互连时,必须确保它们的DOMAIN ID不同,因为相同ID会导致冲突和通信问题。默认情况下,大多数博科交换机的DOMAIN ID设置为1,因此我们需要将其中一个交换机的ID更改为其他值。 ...

    行业分类-设备装置-IP多媒体子系统域与电路交换域互通消息的系统及方法.zip

    在现代通信技术中,IP多媒体子系统(IMS)与电路交换域(CS Domain)的互通是网络演进的关键环节。IMS是一种基于IP网络的架构,主要用于提供多媒体通信服务,如语音、视频通话、即时消息等。而CS域则是传统电话网络...

    使用Xtext和Xtend实现域特定语言(第二版)-中文翻译-第一章

    虽然XML是一种广泛使用的数据交换格式,它对机器友好,但对人类阅读者来说通常不够直观。XML的结构通过标签和属性来表示数据,这可能会导致信息的表达冗长且复杂,从而分散了人类读写XML时的注意力。此外,使用XML...

    text_5.rar_跨时钟域信号_跨时钟域控制信号处理电路

    通过这种方式,可以确保在正确的时刻进行数据交换,防止数据丢失或损坏。 为了实现这种控制信号处理,设计者需要考虑以下关键点: 1. **握手信号**:如前所述,握手协议是确保数据同步的关键。它确保了发送和接收...

    ApplicationDomain的误解,安全沙箱有关内容

    例如,可以指定某些代码只能在其自己的`ApplicationDomain`内部运行,或者允许某些代码跨越安全边界进行数据交换。 #### 四、ApplicationDomain的实例操作 下面通过一段示例代码来具体说明如何在AS3中使用`...

    Multi-Clock Domain data synchronization

    4. 时钟域交叉(CDC)技术:如异步 FIFO、握手协议、多相锁定环等,这些技术专门用于处理时钟域之间的数据交换,确保数据的准确性和完整性。 四、异步时钟域同步实例 异步 FIFO 是一种常见的 CDC 解决方案,它在两个...

    计算机网络实验4windows网络域的实现.pdf

    信任关系允许不同域之间的安全信息交换,使得用户可以在权限范围内访问其他域的资源。Windows Server 2003中的信任关系具有双向性和可传递性。 6. **角色与服务器** - **域控制器(DC)**:存储和维护活动目录,...

    45-FPGA跨时钟域双口RAM设计.7z

    在FPGA应用中,跨时钟域的双口RAM常用于缓存、数据交换和队列等场景,有效避免了时钟域同步问题导致的数据丢失或错误。 首先,我们需要了解Verilog HDL,这是一种硬件描述语言,用于描述数字系统的结构和行为。在...

    基于FPGA的“龙鳞”通信模块跨时钟域验证实践.pdf

    在这些时钟域之间进行数据交换时,必须通过适当的同步机制以保证信号传输的可靠性。 为了解决跨时钟域问题,通常采用以下几种方法: 1. 双触发器(Double Flip-Flop)方法:将数据通过两个触发器(通常位于目标...

    domain配置.pdf

    通过这种方式,不同域之间可以进行数据交换和服务调用,从而实现分布式应用的无缝集成。 ### 高级配置:提升域管理效率与安全性 除了基本的配置,Tuxedo还提供了丰富的高级配置选项,以增强域的管理效率和安全性。...

    TUXEDO_DOMAIN

    TUXEDO域的管理模型涉及单域模式和多域模式,其中单域模式下,服务进程在单一主机上运行或在多个主机上分散,服务进程间通过共享内存或网络交换数据。而在多域模式中,应用程序由多个单域模块组成,域之间可以通过...

    handshake_handshake_跨时钟域信号_

    标题中的“handshake”指的是握手协议,这是一种在不同时钟域之间进行数据交换的常用方法。本文将详细介绍跨时钟域信号处理中的握手信号机制以及其在接收端和发送端的应用。 握手协议是一种同步通信协议,通过信号...

    PS域2G3G互操作(HW版本)

    PS域(Packet Switched Domain,分组交换域)是GPRS和UMTS网络中的数据传输部分,它与CS域(Circuit Switched Domain,电路交换域)并存,主要负责非实时的数据服务。在PS域中,2G到3G的互操作主要包括以下步骤: 1...

    LocalConnection域访问详解

    LocalConnection对象是ActionScript 3.0中用于在同一台计算机上的不同Flash应用程序之间实现通信的机制。这个功能在Flex开发中特别有用,因为有时我们可能需要在不同的SWF文件之间交换数据或控制逻辑,尤其是在处理...

    Domain_Decomposition

    这些子域可以分布在不同的处理器上进行并行计算,而相邻子域之间则通过数据交换来同步粒子状态。 #### GROMACS 4.0的新特性 除了域分解技术外,GROMACS 4.0还包括了许多其他新特性: - **邻域列表更新的启发式...

    unix domain socket传递描述符

    与网络套接字不同,Unix域套接字不涉及网络传输,而是通过文件系统路径来定位和连接,因此效率更高,常用于在同一台机器上运行的进程之间的高效数据交换。 Unix域套接字有两种类型:流式(SOCK_STREAM)和数据报式...

Global site tag (gtag.js) - Google Analytics