“相同來(lái)源”(相同站點(diǎn))策略限制了窗口和框架之間的訪問(wèn)。
這樣的想法是,如果用戶打開(kāi)了兩個(gè)頁(yè)面:一個(gè)來(lái)自john-smith.com,而另一個(gè)打開(kāi)gmail.com,則他們將不需要腳本來(lái)john-smith.com從中讀取我們的郵件gmail.com。因此,“相同來(lái)源”策略的目的是保護(hù)用戶免遭信息盜竊。
同源
如果兩個(gè)URL具有相同的協(xié)議,域和端口,則稱它們具有“相同的來(lái)源”。
這些URL都具有相同的來(lái)源:
http://http:///http:///my/page.html
這些沒(méi)有:
http://www.(另一個(gè)領(lǐng)域:www.重要)http://site.org(另一個(gè)領(lǐng)域:.org重要)https://(另一種協(xié)議:https)http://:8080(另一個(gè)端口:8080)
“相同來(lái)源”政策規(guī)定:
- 如果我們引用了另一個(gè)窗口,例如由創(chuàng)建的彈出
window.open窗口或內(nèi)部的一個(gè)窗口<iframe>,并且該窗口來(lái)自同一原點(diǎn),則可以完全訪問(wèn)該窗口。 - 否則,如果它來(lái)自另一個(gè)來(lái)源,那么我們將無(wú)法訪問(wèn)該窗口的內(nèi)容:變量,文檔等。唯一的例外是
location:我們可以更改它(從而重定向用戶)。但是我們無(wú)法讀取位置(因此我們無(wú)法看到用戶現(xiàn)在所在的位置,也不會(huì)泄漏任何信息)。
實(shí)際應(yīng)用中:iframe
一個(gè)<iframe>標(biāo)簽主機(jī)單獨(dú)的嵌入式窗口,有自己的獨(dú)立document和window對(duì)象。
我們可以使用屬性訪問(wèn)它們:
iframe.contentWindow使窗戶進(jìn)入窗戶<iframe>。iframe.contentDocument將文檔放入的<iframe>簡(jiǎn)寫iframe.contentWindow.document。
當(dāng)我們?cè)L問(wèn)嵌入式窗口內(nèi)的內(nèi)容時(shí),瀏覽器會(huì)檢查iframe的來(lái)源是否相同。如果不是這樣,則訪問(wèn)被拒絕(寫入location是一個(gè)例外,它仍然被允許)。
例如,讓我們嘗試<iframe>從另一個(gè)來(lái)源進(jìn)行讀寫:
<iframe src="https://" id="iframe"></iframe>
<script>
iframe.onload = function() {
// we can get the reference to the inner window
let iframeWindow = iframe.contentWindow; // OK
try {
// ...but not to the document inside it
let doc = iframe.contentDocument; // ERROR
} catch(e) {
alert(e); // Security Error (another origin)
}
// also we can't READ the URL of the page in iframe
try {
// Can't read URL from the Location object
let href = iframe.contentWindow.location.href; // ERROR
} catch(e) {
alert(e); // Security Error
}
// ...we can WRITE into location (and thus load something else into the iframe)!
iframe.contentWindow.location = '/'; // OK
iframe.onload = null; // clear the handler, not to run it after the location change
};
</script>
上面的代碼顯示了除以下各項(xiàng)以外的所有操作的錯(cuò)誤:
- 獲取對(duì)內(nèi)部窗口的引用
iframe.contentWindow-允許的。 - 寫信給
location。
與此相反,如果<iframe>起源相同,我們可以使用它做任何事情:
<!-- iframe from the same site -->
<iframe src="/" id="iframe"></iframe>
<script>
iframe.onload = function() {
// just do anything
iframe.contentDocument.body.prepend("Hello, world!");
};
</script>
iframe.onload 與 iframe.contentWindow.onload的iframe.onload(在事件<iframe>標(biāo)簽)是基本上相同iframe.contentWindow.onload(在嵌入窗口對(duì)象)。當(dāng)嵌入式窗口完全加載所有資源時(shí)觸發(fā)。
…但是我們無(wú)法iframe.contentWindow.onload從其他來(lái)源訪問(wèn)iframe,因此請(qǐng)使用iframe.onload。
Windows在子域上:document.domain
根據(jù)定義,兩個(gè)具有不同域的URL具有不同的來(lái)源。
但是,如果窗口共享同一個(gè)二級(jí)域名,例如john.,peter.和(使他們共同的二級(jí)域名),我們可以讓瀏覽器忽略這種差別,使他們能夠從“同根同源”來(lái)對(duì)待為了跨窗口交流的目的。
為了使其正常工作,每個(gè)這樣的窗口都應(yīng)運(yùn)行以下代碼:
document.domain = '';
就這樣。現(xiàn)在,他們可以不受限制地進(jìn)行交互。同樣,這僅適用于具有相同二級(jí)域的頁(yè)面。
iframe:錯(cuò)誤的文檔陷阱
當(dāng)iframe來(lái)自同一來(lái)源時(shí),我們可能會(huì)訪問(wèn)它 document,這是一個(gè)陷阱。它與跨源事物無(wú)關(guān),但重要的是要知道。
創(chuàng)建后,iframe會(huì)立即擁有一個(gè)文檔。但是該文檔不同于加載到其中的文檔!
因此,如果我們立即對(duì)文檔進(jìn)行操作,則可能會(huì)丟失。
在這里,看看:
<iframe src="/" id="iframe"></iframe>
<script>
let oldDoc = iframe.contentDocument;
iframe.onload = function() {
let newDoc = iframe.contentDocument;
// the loaded document is not the same as initial!
alert(oldDoc == newDoc); // false
};
</script>
我們不應(yīng)該處理尚未加載的iframe的文檔,因?yàn)槟鞘?/span>錯(cuò)誤的文檔。如果我們?cè)谄渖显O(shè)置了任何事件處理程序,它們將被忽略。
如何檢測(cè)文檔在那里的時(shí)刻?
iframe.onload 觸發(fā)時(shí),正確的文檔肯定存在。但是,只有在加載了所有資源的整個(gè)iframe時(shí),它才會(huì)觸發(fā)。
我們可以嘗試使用check in來(lái)趕上這一時(shí)刻setInterval:
<iframe src="/" id="iframe"></iframe>
<script>
let oldDoc = iframe.contentDocument;
// every 100 ms check if the document is the new one
let timer = setInterval(() => {
let newDoc = iframe.contentDocument;
if (newDoc == oldDoc) return;
alert("New document is here!");
clearInterval(timer); // cancel setInterval, don't need it any more
}, 100);
</script>
集合:window.frames
獲取–的窗口對(duì)象的另一種方法<iframe>是從命名集合中獲取它 window.frames:
- 按編號(hào):
window.frames[0]–文檔中第一幀的窗口對(duì)象。 - 按名稱:
window.frames.iframeName–具有的框架的窗口對(duì)象name="iframeName"。
例如:
<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ì)象形成層次結(jié)構(gòu)。
導(dǎo)航鏈接為:
window.frames–“子級(jí)”窗口的集合(用于嵌套框架)。window.parent–對(duì)“父”(外部)窗口的引用。window.top–對(duì)最頂層父窗口的引用。
例如:
window.frames[0].parent === window; // true
我們可以使用該top屬性來(lái)檢查當(dāng)前文檔是否在框架內(nèi)打開(kāi):
if (window == top) { // current window == window.top?
alert('The script is in the topmost window, not in a frame');
} else {
alert('The script runs in a frame!');
}
“沙盒” iframe屬性
該sandbox屬性允許排除<iframe>in 內(nèi)部的某些操作,以防止其執(zhí)行不受信任的代碼。它通過(guò)將iframe視為來(lái)自其他來(lái)源和/或應(yīng)用其他限制來(lái)對(duì)其進(jìn)行“沙盒化”。
有適用于的“默認(rèn)設(shè)置”限制<iframe sandbox src="...">。但是,如果我們提供一個(gè)以空格分隔的限制列表,可以將其放寬,這些限制不應(yīng)用作屬性的值,例如:<iframe sandbox="allow-forms allow-popups">。
換句話說(shuō),空"sandbox"屬性會(huì)施加最嚴(yán)格的限制,但我們可以放置一個(gè)用空格分隔的列表,以列出要?jiǎng)h除的屬性。
以下是限制的列表:
allow-same-origin- 默認(rèn)情況下
"sandbox",對(duì)iframe強(qiáng)制實(shí)施“不同來(lái)源”策略。換句話說(shuō),它使瀏覽器將iframe視為來(lái)自另一個(gè)來(lái)源,即使其src指向同一站點(diǎn)也是如此。具有所有隱含的腳本限制。此選項(xiàng)將刪除該功能。 allow-top-navigation- 允許
iframe更改parent.location。 allow-forms- 允許提交來(lái)自的表格
iframe。 allow-scripts- 允許從運(yùn)行腳本
iframe。 allow-popups- 允許從
window.open彈出窗口iframe
有關(guān)更多信息,請(qǐng)參見(jiàn)手冊(cè)。
以下示例演示了具有默認(rèn)限制集的沙盒iframe <iframe sandbox src="...">。它具有一些JavaScript和一種形式。
請(qǐng)注意,沒(méi)有任何效果。因此,默認(rèn)設(shè)置確實(shí)很苛刻:
該"sandbox"屬性的目的只是添加更多限制。它無(wú)法刪除它們。特別是,如果iframe來(lái)自其他來(lái)源,則無(wú)法放寬同源限制。
跨窗口消息傳遞
該postMessage界面允許Windows彼此交談,無(wú)論它們來(lái)自哪個(gè)來(lái)源。
因此,這是解決“相同來(lái)源”政策的一種方法。它允許從窗口john-smith.com進(jìn)行對(duì)話gmail.com和交換信息,但前提是它們都同意并調(diào)用相應(yīng)的JavaScript函數(shù)。這對(duì)用戶來(lái)說(shuō)很安全。
該界面分為兩個(gè)部分。
postMessage
想要發(fā)送消息的窗口調(diào)用接收窗口的postMessage方法。換句話說(shuō),如果要將消息發(fā)送到win,則應(yīng)致電 win.postMessage(data, targetOrigin)。
參數(shù):
data- 要發(fā)送的數(shù)據(jù)。可以是任何對(duì)象,可以使用“結(jié)構(gòu)化序列化算法”克隆數(shù)據(jù)。IE僅支持字符串,因此我們應(yīng)該使用
JSON.stringify復(fù)雜的對(duì)象來(lái)支持該瀏覽器。 targetOrigin- 指定目標(biāo)窗口的原點(diǎn),以便只有給定原點(diǎn)的窗口才能獲取消息。
這targetOrigin是一項(xiàng)安全措施。請(qǐng)記住,如果目標(biāo)窗口來(lái)自另一個(gè)來(lái)源,我們將無(wú)法location在發(fā)送方窗口中讀取它。因此,我們不能確定現(xiàn)在正在預(yù)期的窗口中打開(kāi)了哪個(gè)站點(diǎn):用戶可以導(dǎo)航離開(kāi),并且發(fā)件人窗口對(duì)此一無(wú)所知。
指定該選項(xiàng)targetOrigin可確保窗口僅在正確位置時(shí)才接收數(shù)據(jù)。當(dāng)數(shù)據(jù)敏感時(shí)很重要。
例如,win僅在源頭有文檔的情況下,此處才接收消息http://:
<iframe src="http://" name="example">
<script>
let win = window.frames.example;
win.postMessage("message", "http://");
</script>
如果我們不想檢查,可以將其設(shè)置targetOrigin為*。
<iframe src="http://" name="example">
<script>
let win = window.frames.example;
win.postMessage("message", "*");
</script>
消息
要接收消息,目標(biāo)窗口應(yīng)具有message事件處理程序。觸發(fā)何時(shí)postMessage調(diào)用(并targetOrigin檢查成功)。
事件對(duì)象具有特殊的屬性:
data- 來(lái)自的數(shù)據(jù)
postMessage。 origin- 例如,發(fā)件人的來(lái)源
http://。 source- 對(duì)發(fā)件人窗口的引用。
source.postMessage(...)如果需要,我們可以立即返回。
要分配該處理程序,我們應(yīng)該使用addEventListener,短語(yǔ)法window.onmessage不起作用。
這是一個(gè)例子:
window.addEventListener("message", function(event) {
if (event.origin != 'http://') {
// something from an unknown domain, let's ignore it
return;
}
alert( "received: " + event.data );
// can message back using event.source.postMessage(...)
});
完整的例子:
概要
要調(diào)用方法并訪問(wèn)另一個(gè)窗口的內(nèi)容,我們應(yīng)該首先對(duì)其進(jìn)行引用。
對(duì)于彈出窗口,我們有以下參考:
- 在打開(kāi)器窗口中:
window.open–打開(kāi)一個(gè)新窗口并返回對(duì)該窗口的引用, - 從彈出窗口:
window.opener–是對(duì)彈出窗口中打開(kāi)器窗口的引用。
對(duì)于iframe,我們可以使用以下方法訪問(wèn)父/子窗口:
window.frames–嵌套窗口對(duì)象的集合,window.parent,window.top是對(duì)父窗口和頂部窗口的引用,iframe.contentWindow是<iframe>標(biāo)簽內(nèi)的窗口。
如果Windows共享相同的來(lái)源(主機(jī),端口,協(xié)議),則Windows可以相互執(zhí)行任何操作。
否則,只有可能的操作是:
- 更改
location另一個(gè)窗口的(只讀訪問(wèn))。 - 向其發(fā)布消息。
例外是:
- 共享相同二級(jí)域的Windows:
a.和b.。然后document.domain=''將它們都設(shè)置為“原點(diǎn)相同”狀態(tài)。 - 如果iframe具有
sandbox屬性,則除非allow-same-origin該屬性值中指定,否則它將被強(qiáng)制置于“其他來(lái)源”狀態(tài)。這可用于在同一站點(diǎn)的iframe中運(yùn)行不受信任的代碼。
該postMessage界面允許兩個(gè)具有任何起源的窗口進(jìn)行交談:
-
發(fā)件人呼叫
targetWin.postMessage(data, targetOrigin)。 -
如果
targetOrigin不是'*',則瀏覽器檢查windowtargetWin是否具有原點(diǎn)targetOrigin。 -
如果是這樣,則使用特殊屬性
targetWin觸發(fā)message事件:origin–發(fā)送者窗口的來(lái)源(如http://my.)source–對(duì)發(fā)件人窗口的引用。data–數(shù)據(jù),除IE僅支持字符串的任何地方的任何對(duì)象。
我們應(yīng)該使用
addEventListener它在目標(biāo)窗口內(nèi)設(shè)置此事件的處理程序。





