W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
“同源(Same Origin)”策略限制了窗口(window)和 frame 之間的相互訪問(wèn)。
這個(gè)想法出于這樣的考慮,如果一個(gè)用戶有兩個(gè)打開(kāi)的頁(yè)面:一個(gè)來(lái)自 john-smith.com
,另一個(gè)是 gmail.com
,那么用戶將不希望 john-smith.com
的腳本可以讀取 gmail.com
中的郵件。所以,“同源”策略的目的是保護(hù)用戶免遭信息盜竊。
如果兩個(gè) URL 具有相同的協(xié)議,域和端口,則稱它們是“同源”的。
以下的幾個(gè) URL 都是同源的:
http://site.com
?http://site.com/
?http://site.com/my/page.html
?但是下面這幾個(gè)不是:
http://www.site.com
?(另一個(gè)域:?www.
? 影響)http://site.org
?(另一個(gè)域:?.org
? 影響)https://site.com
?(另一個(gè)協(xié)議:?https
?)http://site.com:8080
?(另一個(gè)端口:?8080
?)“同源”策略規(guī)定:
window.open
? 創(chuàng)建的彈窗,或者一個(gè)窗口中的 iframe)的引用,并且該窗口是同源的,那么我們就具有對(duì)該窗口的全部訪問(wèn)權(quán)限。location
?:我們可以修改它(進(jìn)而重定向用戶)。但是我們無(wú)法讀取 ?location
?(因此,我們無(wú)法看到用戶當(dāng)前所處的位置,也就不會(huì)泄漏任何信息)。一個(gè) <iframe>
標(biāo)簽承載了一個(gè)單獨(dú)的嵌入的窗口,它具有自己的 document
和 window
。
我們可以使用以下屬性訪問(wèn)它們:
iframe.contentWindow
? 來(lái)獲取 ?<iframe>
? 中的 window。iframe.contentDocument
? 來(lái)獲取 ?<iframe>
? 中的 document,是 ?iframe.contentWindow.document
? 的簡(jiǎn)寫(xiě)形式。當(dāng)我們?cè)L問(wèn)嵌入的窗口中的東西時(shí),瀏覽器會(huì)檢查 iframe 是否具有相同的源。如果不是,則會(huì)拒絕訪問(wèn)(對(duì) location
進(jìn)行寫(xiě)入是一個(gè)例外,它是會(huì)被允許的)。
例如,讓我們嘗試對(duì)來(lái)自另一個(gè)源的 <iframe>
進(jìn)行讀取和寫(xiě)入:
<iframe src="https://example.com" rel="external nofollow" id="iframe"></iframe>
<script>
iframe.onload = function() {
// 我們可以獲取對(duì)內(nèi)部 window 的引用
let iframeWindow = iframe.contentWindow; // OK
try {
// ...但是無(wú)法獲取其中的文檔
let doc = iframe.contentDocument; // ERROR
} catch(e) {
alert(e); // Security Error(另一個(gè)源)
}
// 并且,我們也無(wú)法讀取 iframe 中頁(yè)面的 URL
try {
// 無(wú)法從 location 對(duì)象中讀取 URL
let href = iframe.contentWindow.location.href; // ERROR
} catch(e) {
alert(e); // Security Error
}
// ...我們可以寫(xiě)入 location(所以,在 iframe 中加載了其他內(nèi)容)!
iframe.contentWindow.location = '/'; // OK
iframe.onload = null; // 清空處理程序,在 location 更改后不要再運(yùn)行它
};
</script>
上述代碼除了以下操作都會(huì)報(bào)錯(cuò):
iframe.contentWindow
? 獲取對(duì)內(nèi)部 window 的引用 —— 這是被允許的。location
? 進(jìn)行寫(xiě)入與此相反,如果 <iframe>
具有相同的源,我們可以使用它做任何事情:
<!-- 來(lái)自同一個(gè)網(wǎng)站的 iframe -->
<iframe src="/" id="iframe"></iframe>
<script>
iframe.onload = function() {
// 可以做任何事兒
iframe.contentDocument.body.prepend("Hello, world!");
};
</script>
?
iframe.onload
? vs ?iframe.contentWindow.onload
?
iframe.onload
事件(在<iframe>
標(biāo)簽上)與iframe.contentWindow.onload
(在嵌入的 window 對(duì)象上)基本相同。當(dāng)嵌入的窗口的所有資源都完全加載完畢時(shí)觸發(fā)。
……但是,我們無(wú)法使用
iframe.contentWindow.onload
訪問(wèn)不同源的 iframe。因此,請(qǐng)使用iframe.onload
,
根據(jù)定義,兩個(gè)具有不同域的 URL 具有不同的源。
但是,如果窗口的二級(jí)域相同,例如 john.site.com
,peter.site.com
和 site.com
(它們共同的二級(jí)域是 site.com
),我們可以使瀏覽器忽略該差異,使得它們可以被作為“同源”的來(lái)對(duì)待,以便進(jìn)行跨窗口通信。
為了做到這一點(diǎn),每個(gè)這樣的窗口都應(yīng)該執(zhí)行下面這行代碼:
document.domain = 'site.com';
這樣就可以了。現(xiàn)在它們可以無(wú)限制地進(jìn)行交互了。但是再?gòu)?qiáng)調(diào)一遍,這僅適用于具有相同二級(jí)域的頁(yè)面。
已棄用,但仍有效
document.domain
屬性正在被從 規(guī)范 中刪除??绱翱谕ㄐ牛ㄏ旅鎸⒑芸旖忉尩剑┦墙ㄗh的替代方案。
也就是說(shuō),到目前為止,所有瀏覽器都支持它。并且未來(lái)也將繼續(xù)支持它,而不會(huì)導(dǎo)致使用了
document.domain
的舊代碼出現(xiàn)問(wèn)題。
當(dāng)一個(gè) iframe 來(lái)自同一個(gè)源時(shí),我們可能會(huì)訪問(wèn)其 document
,但是這里有一個(gè)陷阱。它與跨源無(wú)關(guān),但你一定要知道。
在創(chuàng)建 iframe 后,iframe 會(huì)立即就擁有了一個(gè)文檔。但是該文檔不同于加載到其中的文檔!
因此,如果我們要立即對(duì)文檔進(jìn)行操作,就可能出問(wèn)題。
看一下下面這段代碼:
<iframe src="/" id="iframe"></iframe>
<script>
let oldDoc = iframe.contentDocument;
iframe.onload = function() {
let newDoc = iframe.contentDocument;
// 加載的文檔與初始的文檔不同!
alert(oldDoc == newDoc); // false
};
</script>
我們不應(yīng)該對(duì)尚未加載完成的 iframe 的文檔進(jìn)行處理,因?yàn)槟鞘?nbsp;錯(cuò)誤的文檔。如果我們?cè)谄渖显O(shè)置了任何事件處理程序,它們將會(huì)被忽略。
如何檢測(cè)文檔就位(加載完成)的時(shí)刻呢?
正確的文檔在 iframe.onload
觸發(fā)時(shí)肯定就位了。但是,只有在整個(gè) iframe 和它所有資源都加載完成時(shí),iframe.onload
才會(huì)觸發(fā)。
我們可以嘗試通過(guò)在 setInterval
中進(jìn)行檢查,以更早地捕獲該時(shí)刻:
<iframe src="/" id="iframe"></iframe>
<script>
let oldDoc = iframe.contentDocument;
// 每 100ms 檢查一次文檔是否為新文檔
let timer = setInterval(() => {
let newDoc = iframe.contentDocument;
if (newDoc == oldDoc) return;
alert("New document is here!");
clearInterval(timer); // 取消 setInterval,不再需要它做任何事兒
}, 100);
</script>
獲取 <iframe>
的 window 對(duì)象的另一個(gè)方式是從命名集合 window.frames
中獲?。?
window.frames[0]
? —— 文檔中的第一個(gè) iframe 的 window 對(duì)象。window.frames.iframeName
? —— 獲取 ?name="iframeName"
? 的 iframe 的 window 對(duì)象。例如:
<iframe src="/" style="height:80px" name="win" id="iframe"></iframe>
<script>
alert(iframe.contentWindow == frames[0]); // true
alert(iframe.contentWindow == frames.win); // true
</script>
一個(gè) iframe 內(nèi)可能嵌套了其他的 iframe。相應(yīng)的 window
對(duì)象會(huì)形成一個(gè)層次結(jié)構(gòu)(hierarchy)。
可以通過(guò)以下方式獲?。?/p>
window.frames
? —— “子”窗口的集合(用于嵌套的 iframe)。window.parent
? —— 對(duì)“父”(外部)窗口的引用。window.top
? —— 對(duì)最頂級(jí)父窗口的引用。例如:
window.frames[0].parent === window; // true
我們可以使用 top
屬性來(lái)檢查當(dāng)前的文檔是否是在 iframe 內(nèi)打開(kāi)的:
if (window == top) { // 當(dāng)前 window == window.top?
alert('The script is in the topmost window, not in a frame');
} else {
alert('The script runs in a frame!');
}
sandbox
特性(attribute)允許在 <iframe>
中禁止某些特定行為,以防止其執(zhí)行不被信任的代碼。它通過(guò)將 iframe 視為非同源的,或者應(yīng)用其他限制來(lái)實(shí)現(xiàn) iframe 的“沙盒化”。
對(duì)于 <iframe sandbox src="...">
,有一個(gè)應(yīng)用于其上的默認(rèn)的限制集。但是,我們可以通過(guò)提供一個(gè)以空格分隔的限制列表作為特性的值,來(lái)放寬這些限制,該列表中的各項(xiàng)為不應(yīng)該應(yīng)用于這個(gè) iframe 的限制,例如:<iframe sandbox="allow-forms allow-popups">
。
換句話說(shuō),一個(gè)空的 "sandbox"
特性會(huì)施加最嚴(yán)格的限制,但是我們用一個(gè)以空格分隔的列表,列出要移除的限制。
以下是限制的列表:
?allow-same-origin
?
默認(rèn)情況下,?"sandbox"
? 會(huì)為 iframe 強(qiáng)制實(shí)施“不同來(lái)源”的策略。換句話說(shuō),它使瀏覽器將 ?iframe
? 視為來(lái)自另一個(gè)源,即使其 ?src
? 指向的是同一個(gè)網(wǎng)站也是如此。具有所有隱含的腳本限制。此選項(xiàng)會(huì)移除這些限制。
?allow-top-navigation
?
允許 ?iframe
? 更改 ?parent.location
?。
?allow-forms
?
允許在 ?iframe
? 中提交表單。
?allow-scripts
?
允許在 ?iframe
? 中運(yùn)行腳本。
?allow-popups
?
允許在 ?iframe
? 中使用 ?window.open
? 打開(kāi)彈窗。
查看 官方手冊(cè) 獲取更多內(nèi)容。
下面的示例演示了一個(gè)具有默認(rèn)限制集的沙盒 iframe:<iframe sandbox src="...">
。它有一些 JavaScript 代碼和一個(gè)表單。
請(qǐng)注意,這里沒(méi)有東西會(huì)運(yùn)行??梢?jiàn)默認(rèn)設(shè)置非??量蹋?
請(qǐng)注意:
"sandbox"
特性的目的僅是 添加更多 限制。它無(wú)法移除這些限制。尤其是,如果 iframe 來(lái)自其他源,則無(wú)法放寬同源策略。
?postMessage
? 接口允許窗口之間相互通信,無(wú)論它們來(lái)自什么源。
因此,這是解決“同源”策略的方式之一。它允許來(lái)自于 john-smith.com
的窗口與來(lái)自于 gmail.com
的窗口進(jìn)行通信,并交換信息,但前提是它們雙方必須均同意并調(diào)用相應(yīng)的 JavaScript 函數(shù)。這可以保護(hù)用戶的安全。
這個(gè)接口有兩個(gè)部分。
想要發(fā)送消息的窗口需要調(diào)用接收窗口的 postMessage 方法。換句話說(shuō),如果我們想把消息發(fā)送給 win
,我們應(yīng)該調(diào)用 win.postMessage(data, targetOrigin)
。
參數(shù):
?data
?
要發(fā)送的數(shù)據(jù)。可以是任何對(duì)象,數(shù)據(jù)會(huì)被通過(guò)使用“結(jié)構(gòu)化序列化算法(structured serialization algorithm)”進(jìn)行克隆。IE 瀏覽器只支持字符串,因此我們需要對(duì)復(fù)雜的對(duì)象調(diào)用 ?JSON.stringify
? 方法進(jìn)行處理,以支持該瀏覽器。
?targetOrigin
?
指定目標(biāo)窗口的源,以便只有來(lái)自給定的源的窗口才能獲得該消息。
targetOrigin
是一種安全措施。請(qǐng)記住,如果目標(biāo)窗口是非同源的,我們無(wú)法在發(fā)送方窗口讀取它的 location
。因此,我們無(wú)法確定當(dāng)前在預(yù)期的窗口中打開(kāi)的是哪個(gè)網(wǎng)站:用戶隨時(shí)可以導(dǎo)航離開(kāi),并且發(fā)送方窗口對(duì)此一無(wú)所知。
指定 targetOrigin
可以確保窗口僅在當(dāng)前仍處于正確的網(wǎng)站時(shí)接收數(shù)據(jù)。在有敏感數(shù)據(jù)時(shí),這非常重要。
例如,這里的 win
僅在它擁有來(lái)自 http://example.com
這個(gè)源的文檔時(shí),才會(huì)接收消息:
<iframe src="http://example.com" rel="external nofollow" rel="external nofollow" name="example">
<script>
let win = window.frames.example;
win.postMessage("message", "http://example.com");
</script>
如果我們不希望做這個(gè)檢查,可以將 targetOrigin
設(shè)置為 *
。
<iframe src="http://example.com" rel="external nofollow" rel="external nofollow" name="example">
<script>
let win = window.frames.example;
win.postMessage("message", "*");
</script>
為了接收消息,目標(biāo)窗口應(yīng)該在 message
事件上有一個(gè)處理程序。當(dāng) postMessage
被調(diào)用時(shí)觸發(fā)該事件(并且 targetOrigin
檢查成功)。
event 對(duì)象具有特殊屬性:
?data
?
從 ?postMessage
? 傳遞來(lái)的數(shù)據(jù)。
?origin
?
發(fā)送方的源,例如 ?http://javascript.info
?。
?source
?
對(duì)發(fā)送方窗口的引用。如果我們想,我們可以立即 ?source.postMessage(...)
? 回去。
要為 message
事件分配處理程序,我們應(yīng)該使用 addEventListener
,簡(jiǎn)短的語(yǔ)法 window.onmessage
不起作用。
這里有一個(gè)例子:
window.addEventListener("message", function(event) {
if (event.origin != 'http://javascript.info') {
// 來(lái)自未知的源的內(nèi)容,我們忽略它
return;
}
alert( "received: " + event.data );
// 可以使用 event.source.postMessage(...) 向回發(fā)送消息
});
要調(diào)用另一個(gè)窗口的方法或者訪問(wèn)另一個(gè)窗口的內(nèi)容,我們應(yīng)該首先擁有對(duì)其的引用。
對(duì)于彈窗,我們有兩個(gè)引用:
window.open
? —— 打開(kāi)一個(gè)新的窗口,并返回對(duì)它的引用,window.opener
? —— 是從彈窗中對(duì)打開(kāi)此彈窗的窗口(opener)的引用。對(duì)于 iframe,我們可以使用以下方式訪問(wèn)父/子窗口:
window.frames
? —— 一個(gè)嵌套的 window 對(duì)象的集合,window.parent
?,?window.top
? 是對(duì)父窗口和頂級(jí)窗口的引用,iframe.contentWindow
? 是 ?<iframe>
? 標(biāo)簽內(nèi)的 window 對(duì)象。如果幾個(gè)窗口的源相同(域,端口,協(xié)議),那么這幾個(gè)窗口可以彼此進(jìn)行所需的操作。
否則,只能進(jìn)行以下操作:
location
?(只能寫(xiě)入)。例外情況:
a.site.com
? 和 ?b.site.com
?。通過(guò)在這些窗口中均設(shè)置 ?document.domain='site.com'
?,可以使它們處于“同源”狀態(tài)。sandbox
? 特性(attribute),則它會(huì)被強(qiáng)制處于“非同源”狀態(tài),除非在其特性值中指定了 ?allow-same-origin
?。這可用于在同一網(wǎng)站的 iframe 中運(yùn)行不受信任的代碼。postMessage
接口允許兩個(gè)具有任何源的窗口之間進(jìn)行通信:
targetWin.postMessage(data, targetOrigin)
?。targetOrigin
? 不是 ?'*'
?,那么瀏覽器會(huì)檢查窗口 ?targetWin
? 是否具有源 ?targetOrigin
?。targetWin
? 會(huì)觸發(fā)具有特殊的屬性的 ?message
? 事件:origin
? —— 發(fā)送方窗口的源(比如 ?http://my.site.com
?)。source
? —— 對(duì)發(fā)送方窗口的引用。data
? —— 數(shù)據(jù),可以是任何對(duì)象。但是 IE 瀏覽器只支持字符串,因此我們需要對(duì)復(fù)雜的對(duì)象調(diào)用 ?JSON.stringify
? 方法進(jìn)行處理,以支持該瀏覽器。我們應(yīng)該使用 addEventListener
來(lái)在目標(biāo)窗口中設(shè)置 message
事件的處理程序。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: