天天看點

關于document.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>

<!-- 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) -->

<title>Imgage Upload Interface</title>

<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">

newsedit: 位于 news.mycompany.com 的主目錄下,接受使用者的 POST 請求,将編輯界面的新聞元素存儲到背景資料庫

imgupload: 位于 news.mycompany.com 的 upload 子目錄下,接受使用者的 POST請求,将本地的圖檔檔案上傳到伺服器,并傳回圖檔檔案完整的 url。

下面是 imgupload 處理完 POST 請求後傳回的頁面内容,該内容顯示在ImgUpload.html 所占據的彈出視窗中: <html>

<title>File Upload Successfully</title>

<h3>File Uploaded Successfully!</h3>

<!-- 擷取主視窗的句柄 -->

parwin=self.opener;

<!-- 擷取對 img 元素的引用,并用上傳檔案的 url 為 img 元素的 src 屬性賦

值,這樣在用戶端就可以預覽了 -->

<!-- 為了簡化問題,我們将對 img 元素的引用直接寫在程式中 -->

parwin.addnews.UserImg.src="http://news.mycompany.com/img/2003_07/06/1057478464859.gif";

</body>

好了,我們的第一個實驗已經成功了,實驗結果告訴我們:當兩個幀中的内容同屬于一個域時,利用用戶端腳本和視窗句柄在其中傳遞資料是沒有問題的。接下來我 們把 ImgUpload.html 和 imgupload 從 news.mycompany.com 提取出來,部署到img.mycompany.com 的對應目錄下,并修改 NewsEdit.html 中引用ImgUpload.html 時的 url。這樣當我們試着用 JavaScript 從img.yourcompanu.com 向bbs.yourcompany.com 傳遞資料時,浏覽器就會彈出“拒絕通路(Access Denied)” 的錯誤框,表明我們違反了他的安全政策,并且資料無法正常傳遞過來。

我們之是以會遇到“拒絕通路(Access Denied)” 的的錯誤資訊,其原因在于:

“那為什麼即使我的兩個頁面屬于同一個域我還是會遇到‘拒絕通路’的錯誤呀?”如果是這種情況,那就要看你的彈出視窗中的内容是否始終屬于同一個域,看一 下你的 ImgUpload.html 是不是調用了屬于其他域的應用,并且該應用在視窗中重新寫入了内容,如果是這樣那你的彈出視窗就變質了,它最後屬于另外一個域,你當然會遇到“拒絕通路” 的錯誤。

是的,一些浏覽器的開發商、開發團體在開發高版本的浏覽器時對原有政策進行了部分調整,這些調整給我們帶來了一線生機:當兩個頁面在進行資料交換時,浏覽 器會首先比較兩個頁面的 domain 屬性,如果domain 屬性相同,那麼浏覽器就允許它們之間的資料交換,否則就傳回“拒絕通路(Access Denied)”的錯誤。

加入上面的聲明就可以蒙蔽浏覽器,在原本屬于兩個不同域的頁面之間進行資料交換了。但需要注意:隻有把上面的聲明加入到需要進行資料交換的所有檔案中才會 有效,隻在一個域的檔案中加入上面的聲明是不起作用的。另外,聲明部分最好能插入到頁面的 <head></head> 标記中間,這一點也是用腳本進行開發時所被提倡的。有關 JavaScript 中的 document 和 domain 等可以參考http://www.werelight.com/docs/JavaScript_Quick_Reference.htm

“使用這種方法有什麼限制碼?”因該說用這種方法來實作不同域之間的資料傳遞還是有很多的限制的,主要表現為以下兩點:

方案二下面我們來看一下利用 MSIE 提供的對話框能不能解決兩個域之間的資料交換問題首先我來簡單介紹一下 MSIE 對話框:MSIE 提供的 showModalDialog 和showModelessDialog 方法可以用來在一個單獨的幀中顯示一個模态或非模态對話框,兩個方法都通過一個 URI 參數來指定對話框幀中的内容;可選的參數vArguments 用來向對話框幀傳遞任何類型(包括數組類型)的參數;另外還有一個可選的參數 sFeatures 是用來定義對話框幀的顯示效果,如位置、字型等等的;注意 Netscape Navigator 、Mozilla 和 Opera 浏覽器是沒有與之對應的方法的,也就是說除了 MSIE 之外其他幾大浏覽器都不支援用 showModalDialog 或showModelessDialog 顯示對話框,如果你感興趣的話這裡有一篇文章能夠教你如何通過其他方式來模拟一個模态對話框,詳見 Simulating Modal Dialog Windows

“那麼我能不能像方案一中那樣通過強制指定兩個頁面中的 document.domain 屬性來蒙蔽浏覽器,使其認為兩個頁面屬于同一個域呢?”确實有人提出過這種想法,筆者也試着這樣做過,但最後還以失敗而告終:在兩個頁面中強制指定 document.domain 了屬性後,無論兩個頁面是否屬于同一個域,對話框都無法正常識别從首頁面傳遞過來的參數。

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() 列印出結果的内容。

localdialog.html(remotedialog.html) 的源代碼如下所示: <html>

<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();

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">

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。

非常遺憾,實驗結果告訴我們:用對話框是無法實作這種跨域的資料交換的。

注(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."

總結在上面介紹的三種方案中,除方案二尚不能實作在分屬于不同域的幀之間進行資料交換之外,經證明方案一和方案三都是可行的,不過這兩種方案又各有利弊:

另外,如果您有其他可選方案的話,非常感謝您能通過 Email 告知我,以填補我在這面方面的空白,謝謝!

參考資料

What does the IE "Access is Denied" error mean?

http://www.dannyg.com/ref/jsminifaq.html#q15 PROPERTY: Document::domain

http://www.powermct.co.kr/teched/ecma/doc_domain.html Writing Cross-Domain Web Applications

http://www.knownow.com/support/devguide/Tutorials/Cross_Domain.html

Why do I get "access denied"-error in IE when calling a function in another frame?

http://www.faqts.com/knowledge_base/view.phtml/aid/1524/fid/127 about showModalDialog

http://www.codecomments.com/IE_Scripting/message180995.html