Zásada „Stejný původ“ (stejný web) omezuje přístup oken a rámů k sobě navzájem.
Myšlenka je taková, že pokud má uživatel otevřené dvě stránky:jedna z john-smith.com
a další je gmail.com
, pak by nechtěli skript z john-smith.com
pro čtení naší pošty od gmail.com
. Účelem zásady „Stejný původ“ je tedy chránit uživatele před krádeží informací.
Stejný původ
Dvě adresy URL mají „stejný původ“, pokud mají stejný protokol, doménu a port.
Všechny tyto adresy URL sdílejí stejný původ:
http://site.com
http://site.com/
http://site.com/my/page.html
Tyto ne:
http://www.site.com
(jiná doména:www.
záleží)http://site.org
(jiná doména:.org
záleží)https://site.com
(jiný protokol:https
)http://site.com:8080
(jiný port:8080
)
Zásada „Stejný původ“ uvádí, že:
- pokud máme odkaz na jiné okno, např. vyskakovací okno vytvořené
window.open
nebo okno uvnitř<iframe>
a toto okno pochází ze stejného původu, pak máme k tomuto oknu plný přístup. - jinak, pokud pochází z jiného zdroje, pak nebudeme mít přístup k obsahu tohoto okna:proměnné, dokument, nic. Jedinou výjimkou je
location
:můžeme to změnit (tedy přesměrovat uživatele). Ale nemůžeme číst umístění (takže nevidíme, kde se uživatel nyní nachází, nedochází k úniku informací).
V akci:iframe
<iframe>
tag hostí samostatné vložené okno s vlastním samostatným document
a window
objektů.
Můžeme k nim přistupovat pomocí vlastností:
iframe.contentWindow
aby se okno dostalo do<iframe>
.iframe.contentDocument
dostat dokument do<iframe>
, zkratka proiframe.contentWindow.document
.
Když přistupujeme k něčemu uvnitř vloženého okna, prohlížeč zkontroluje, zda má iframe stejný původ. Pokud tomu tak není, je přístup odepřen (zápis na location
je výjimkou, je stále povoleno).
Zkusme například číst a zapisovat do <iframe>
z jiného původu:
<iframe src="https://example.com" 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>
Výše uvedený kód zobrazuje chyby pro všechny operace kromě:
- Získání odkazu na vnitřní okno
iframe.contentWindow
– to je povoleno. - Zápis na
location
.
Na rozdíl od toho, pokud <iframe>
má stejný původ, můžeme s ním dělat cokoli:
<!-- 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
vs iframe.contentWindow.onload
iframe.onload
událost (na <iframe>
tag) je v podstatě stejný jako iframe.contentWindow.onload
(na vloženém objektu okna). Spustí se, když se vložené okno plně načte se všemi prostředky.
…Ale nemůžeme získat přístup k iframe.contentWindow.onload
pro prvek iframe z jiného původu, tedy pomocí iframe.onload
.
Windows na subdoménách:document.domain
Podle definice mají dvě adresy URL s různými doménami různý původ.
Ale pokud Windows sdílejí stejnou doménu druhé úrovně, například john.site.com
, peter.site.com
a site.com
(takže jejich společná doména druhé úrovně je site.com
), můžeme přimět prohlížeč, aby tento rozdíl ignoroval, takže je lze pro účely komunikace napříč okny považovat za pocházející ze „stejného původu“.
Aby to fungovalo, každé takové okno by mělo spustit kód:
document.domain = 'site.com';
To je vše. Nyní mohou komunikovat bez omezení. Opět je to možné pouze pro stránky se stejnou doménou druhé úrovně.
Zastaralé, ale stále funkční
document.domain
vlastnost je v procesu odstraňování ze specifikace. Doporučenou náhradou je zasílání zpráv napříč okny (vysvětleno brzy níže).
To znamená, že od nynějška to všechny prohlížeče podporují. A podpora bude zachována pro budoucnost, abychom neporušili starý kód, který se opírá o document.domain
.
Iframe:úskalí nesprávného dokumentu
Když prvek iframe pochází ze stejného původu a my můžeme přistupovat k jeho document
, je tu úskalí. Nesouvisí s věcmi napříč původy, ale je důležité to vědět.
Po svém vytvoření má iframe okamžitě dokument. Ale tento dokument se liší od toho, který se do něj načítá!
Takže pokud s dokumentem okamžitě něco uděláme, pravděpodobně se to ztratí.
Tady se podívejte:
<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>
Neměli bychom pracovat s dokumentem s dosud nenačteným prvkem iframe, protože to je nesprávný dokument . Pokud na něj nastavíme nějaké obslužné rutiny událostí, budou ignorovány.
Jak zjistit okamžik, kdy tam dokument je?
Správný dokument je určitě na místě, když iframe.onload
spouštěče. Spustí se ale pouze tehdy, když je načten celý iframe se všemi prostředky.
Můžeme se pokusit zachytit okamžik dříve pomocí kontrol v 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>
Kolekce:window.frames
Alternativní způsob, jak získat objekt okna pro <iframe>
– je získat jej z pojmenované kolekce window.frames
:
- Podle čísla:
window.frames[0]
– objekt okna pro první snímek v dokumentu. - Podle jména:
window.frames.iframeName
– objekt okna pro rám sname="iframeName"
.
Například:
<iframe src="/" style="height:80px" name="win" id="iframe"></iframe>
<script>
alert(iframe.contentWindow == frames[0]); // true
alert(iframe.contentWindow == frames.win); // true
</script>
Prvek iframe může obsahovat další prvky iframe. Odpovídající window
objekty tvoří hierarchii.
Navigační odkazy jsou:
window.frames
– kolekce „dětských“ oken (pro vnořené rámečky).window.parent
– odkaz na „nadřazené“ (vnější) okno.window.top
– odkaz na nejvyšší nadřazené okno.
Například:
window.frames[0].parent === window; // true
Můžeme použít top
vlastnost pro kontrolu, zda je aktuální dokument otevřen uvnitř rámce nebo ne:
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!');
}
Atribut iframe „sandbox“
sandbox
atribut umožňuje vyloučení určitých akcí uvnitř <iframe>
aby se zabránilo spouštění nedůvěryhodného kódu. „Sandboxuje“ prvek iframe tím, že jej považuje za pocházející z jiného původu a/nebo uplatňuje jiná omezení.
Pro <iframe sandbox src="...">
platí „výchozí sada“ omezení . Můžeme se ale uklidnit, když poskytneme mezerami oddělený seznam omezení, která by neměla být použita jako hodnota atributu, jako je tento:<iframe sandbox="allow-forms allow-popups">
.
Jinými slovy, prázdné "sandbox"
atribut klade nejpřísnější možná omezení, ale můžeme vložit mezerou oddělený seznam těch, která chceme zrušit.
Zde je seznam omezení:
allow-same-origin
- Ve výchozím nastavení
"sandbox"
vynutí zásadu „jiného původu“ pro prvek iframe. Jinými slovy, přiměje prohlížeč, aby zacházel siframe
jako pocházející z jiného původu, i když jehosrc
ukazuje na stejný web. Se všemi předpokládanými omezeními pro skripty. Tato možnost tuto funkci odstraní. allow-top-navigation
- Povoluje
iframe
pro změnuparent.location
. allow-forms
- Umožňuje odesílat formuláře z
iframe
. allow-scripts
- Umožňuje spouštět skripty z
iframe
. allow-popups
- Umožňuje
window.open
vyskakovací okna ziframe
Další informace naleznete v příručce.
Níže uvedený příklad ukazuje izolovaný prvek iframe s výchozí sadou omezení:<iframe sandbox src="...">
. Má nějaký JavaScript a formulář.
Upozorňujeme, že nic nefunguje. Takže výchozí nastavení je opravdu drsné:
Resultindex.htmlsandboxed.html<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>The iframe below has the <code>sandbox</code> attribute.</div>
<iframe sandbox src="sandboxed.html" style="height:60px;width:90%"></iframe>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<button onclick="alert(123)">Click to run a script (doesn't work)</button>
<form action="http://google.com">
<input type="text">
<input type="submit" value="Submit (doesn't work)">
</form>
</body>
</html>
Poznámka:
Účel "sandbox"
atribut slouží pouze k přidání dalších omezení. Nelze je odstranit. Zejména nemůže uvolnit omezení stejného původu, pokud prvek iframe pochází z jiného původu.
Zasílání zpráv napříč okny
postMessage
rozhraní umožňuje oknům komunikovat mezi sebou bez ohledu na jejich původ.
Jde tedy o způsob, jak obejít politiku „stejného původu“. Umožňuje okno od john-smith.com
mluvit s gmail.com
a vyměňovat si informace, ale pouze pokud oba souhlasí a volají odpovídající funkce JavaScriptu. Díky tomu je pro uživatele bezpečný.
Rozhraní má dvě části.
postMessage
Okno, které chce odeslat zprávu, zavolá metodu postMessage přijímacího okna. Jinými slovy, pokud chceme poslat zprávu na win
, měli bychom zavolat win.postMessage(data, targetOrigin)
.
Argumenty:
data
- Data k odeslání. Může to být libovolný objekt, data jsou klonována pomocí „strukturovaného serializačního algoritmu“. IE podporuje pouze řetězce, takže bychom měli
JSON.stringify
komplexní objekty pro podporu tohoto prohlížeče. targetOrigin
- Určuje zdroj pro cílové okno, takže zprávu obdrží pouze okno z daného zdroje.
targetOrigin
je bezpečnostní opatření. Pamatujte, že pokud cílové okno pochází z jiného zdroje, nemůžeme přečíst jeho location
v okně odesílatele. Nemůžeme si tedy být jisti, který web je v zamýšleném okně právě otevřen:uživatel by mohl odejít a okno odesílatele o tom nemá ani ponětí.
Zadání targetOrigin
zajišťuje, že okno přijímá data pouze v případě, že je stále na správném místě. Důležité, když jsou data citlivá.
Například zde win
obdrží zprávu pouze v případě, že má dokument z původu http://example.com
:
<iframe src="http://example.com" name="example">
<script>
let win = window.frames.example;
win.postMessage("message", "http://example.com");
</script>
Pokud tuto kontrolu nechceme, můžeme nastavit targetOrigin
na *
.
<iframe src="http://example.com" name="example">
<script>
let win = window.frames.example;
win.postMessage("message", "*");
</script>
onmessage
Chcete-li přijmout zprávu, cílové okno by mělo mít handler na message
událost. Spustí se, když postMessage
se nazývá (a targetOrigin
kontrola je úspěšná).
Objekt události má speciální vlastnosti:
data
- Data z
postMessage
. origin
- Původ odesílatele, například
http://javascript.info
. source
- Odkaz na okno odesílatele. Můžeme okamžitě
source.postMessage(...)
zpět, pokud chceme.
K přiřazení tohoto handleru bychom měli použít addEventListener
, krátká syntaxe window.onmessage
nefunguje.
Zde je příklad:
window.addEventListener("message", function(event) {
if (event.origin != 'http://javascript.info') {
// something from an unknown domain, let's ignore it
return;
}
alert( "received: " + event.data );
// can message back using event.source.postMessage(...)
});
Úplný příklad:
Resultiframe.htmlindex.html<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
Receiving iframe.
<script>
window.addEventListener('message', function(event) {
alert(`Received ${event.data} from ${event.origin}`);
});
</script>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form id="form">
<input type="text" placeholder="Enter message" name="message">
<input type="submit" value="Click to send">
</form>
<iframe src="iframe.html" id="iframe" style="display:block;height:60px"></iframe>
<script>
form.onsubmit = function() {
iframe.contentWindow.postMessage(this.message.value, '*');
return false;
};
</script>
</body>
</html>
Shrnutí
Abychom mohli volat metody a přistupovat k obsahu jiného okna, měli bychom na něj mít nejprve odkaz.
Pro vyskakovací okna máme tyto odkazy:
- V okně otevření:
window.open
– otevře nové okno a vrátí na něj odkaz, - Z vyskakovacího okna:
window.opener
– je odkaz na otevírací okno z vyskakovacího okna.
U prvků iframe můžeme přistupovat k oknům rodičů a dětí pomocí:
window.frames
– kolekce vnořených okenních objektů,window.parent
,window.top
jsou odkazy na nadřazená a horní okna,iframe.contentWindow
je okno uvnitř<iframe>
tag.
Pokud okna sdílejí stejný původ (hostitel, port, protokol), mohou si mezi sebou okna dělat, co chtějí.
V opačném případě jsou možné pouze tyto akce:
- Změňte
location
jiného okna (přístup pouze pro zápis). - Napište do něj zprávu.
Výjimky jsou:
- Windows, která sdílejí stejnou doménu druhé úrovně:
a.site.com
ab.site.com
. Poté nastavtedocument.domain='site.com'
v obou je uvádí do stavu „stejného původu“. - Pokud má prvek iframe hodnotu
sandbox
atribut, je násilně uveden do stavu „jiný původ“, pokudallow-same-origin
je uvedeno v hodnotě atributu. To lze použít ke spouštění nedůvěryhodného kódu v prvcích iframe ze stejného webu.
postMessage
rozhraní umožňuje mluvit dvěma oknům s libovolným původem:
-
Odesílatel volá
targetWin.postMessage(data, targetOrigin)
. -
Pokud
targetOrigin
není'*'
, pak prohlížeč zkontroluje, zda oknotargetWin
má původtargetOrigin
. -
Pokud ano, pak
targetWin
spustímessage
událost se speciálními vlastnostmi:origin
– původ okna odesílatele (např.http://my.site.com
)source
– odkaz na okno odesílatele.data
– data, jakýkoli objekt všude kromě IE, který podporuje pouze řetězce.
Měli bychom použít
addEventListener
pro nastavení obsluhy pro tuto událost v cílovém okně.