前端跨域深刻理解

 GitHub:http://liu12fei08fei.github.io/html/3domain.htmljavascript

同源策略(跨域的由來)php

同源策略限制從一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的關鍵的安全機制。html

「同源」是指:協議相同、域名相同、端口相同前端

同源政策的目的:是爲了保證用戶信息的安全,防止惡意的網站竊取數據。java

同源的限制範圍:jquery

1. Cookie、LocalStorage 和 IndexDB 沒法讀取git

2. DOM 沒法得到github

3. AJAX 請求不能發送web

這裏須要明確的一點是:所謂的域跟js的存放服務器沒有關係,好比baidu.com的頁面加載了google.com的js,那麼此js的所在域是baidu.com而不是google.com。也就是說,此時該js能操做baidu.com的頁面對象,而不能操做google.com的頁面對象。ajax

跨域的方法

1、使用JSONP跨域(單項跨域-通常用於獲取數據)

原理:由於經過script標籤引入的js是不受同源策略的限制的(正如前文提到的baidu.com的頁面加載了google.com的js)。因此咱們能夠經過script標籤引入一個js或者是一個其餘後綴形式(如php,jsp等)的文件,此文件返回一個js函數的調用,如返回一個對象:

JSONP_getPerson({

    "name":"怪誕咖啡",

    "age":18,

    "job":"前端攻城獅",

 });

也就是說此文件返回的結果調用了JSONP_getPerson函數,而且把{"name":"怪誕咖啡","age":18,"job":"前端攻城獅"}傳進去,這{"name":"怪誕咖啡","age":18,"job":"前端攻城獅"}是一個對象。此時咱們的頁面中有一個JSONP_getPerson函數,函數JSONP_getPerson就被調用到,而且傳入了一個對象。此時就實現了在本域獲取其餘域數據的功能,也就是跨域。

JSONP_getPerson:前端引入遠程js並定義好JSONP_getPerson函數,注意須要先定義好JSONP_getPerson函數,避免在遠程js加載完成並調用JSONP_getPerson時,此函數不存在

例子代碼:

<script>

function JSONP_getPerson(users) {

    console.dir(users);

}

</script>

<script src="http://xxx/index.php"></script>

// 前端代碼調用script標籤塊必須在函數標籤塊以後

地址說明:http://xxx/index.php,其中的xxx是表示不一樣域名下的文件,測試的時候,把前端和後端代碼放到不一樣的源中測試

<?php

echo 'JSONP_getPerson({

"name":"怪誕咖啡",

"age":18,

"job":"前端攻城獅",

})';//返回一個js函數的調用

?>

爲何script標籤引入的文件不受同源策略的限制?

  1. 由於script標籤引入的文件內容是不能被客戶端的js獲取到的,不會影響到被引用文件的安全,因此不必使script標籤引入的文件遵循瀏覽器的同源策略。
  2. 經過ajax加載的文件內容是可以被客戶端js獲取到的,因此ajax必須遵循同源策略,不然被引入文件的內容會泄漏或者存在其餘風險。

JSONP的缺點則是:

  1. 它只支持GET請求而不支持POST等其它類型的HTTP請求(雖然採用post+動態生成iframe是能夠達到post跨域的目的,但這樣作是一個比較極端的方式,不建議採用)。明確說明:jquery使用POST請求jsonp能夠成功是因爲jquery自動把POST轉化成GET,實際仍是GET請求
  2. 通常get請求能完成全部功能。如:若是須要給其餘域服務器傳送參數能夠在請求後掛參數(注意不要掛隱私數據),即 <script src="http://xxx/getPerson.php?name=Hello&age=18"></script> 

地址說明:http://xxx/getPerson.php?name=Hello&age=18,其中的xxx是表示不一樣域名下的文件,測試的時候,把前端和後端代碼放到不一樣的源中測試

感悟:

  1. JSONP易於實現,可是也會存在一些安全隱患,若是第三方的腳本隨意地執行,那麼它就能夠篡改頁面內容,截獲敏感數據。
  2. 在受信任的雙方傳遞數據,JSONP是很是合適的選擇。
  3. 能夠看出來JSONP跨域通常用於獲取其餘域的數據。
  4. 通常可以用JSONP實現跨域就用JSONP實現,這也是前端用的最多的跨域方法。

2、動態建立script標籤(單項跨域-通常用於獲取數據)

這種方法實際上是JSONP跨域的簡化版,JSONP只是在此基礎上加入了回調函數。好比上例中的 getPerson.php 返回的若是不是一個js函數的調用,而是一個js變量,如:

<?php echo 'var person = {"name":"怪誕咖啡","age":18,"job":"前端攻城獅"}' ?>

那麼在當前域下就能夠取到person變量,這裏須要注意判斷script節點是否加載完畢,如:

<script>

var head= document.getElementsByTagName('head')[0];

var scriptPerson= document.createElement('script');

scriptPerson.type= 'text/javascript';

scriptPerson.src= 'http://xxx/index.php';

head.appendChild(scriptPerson);

 

scriptPerson.onload = scriptPerson.onreadystatechange = function() {

    if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {

        console.log(person); //此處取出其餘域的數據

        scriptPerson.onload = scriptPerson.onreadystatechange = null;

    }

};

</script>

地址說明:http://xxx/index.php,其中的xxx是表示不一樣域名下的文件,測試的時候,把前端和後端代碼放到不一樣的源中測試

3、window.name屬性(單項跨域-通常用於獲取數據)

來源:

  1. window.name 傳輸技術,本來是 Thomas Frank 用於解決 cookie 的一些劣勢(每一個域名 4 x 20 Kb的限制、數據只能是字符串、設置和獲取 cookie 語法的複雜等等)而發明的。後來 Kris Zyp 在此方法的基礎上強化了 window.name 傳輸 ,用來解決跨域數據傳輸問題。
  2. window.name 的美妙之處:name值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值(2MB)。

特徵:

  1. 瀏覽器窗口有 window.name 屬性。這個屬性的最大特色是,不管是否同源,只要在同一個窗口裏,前一個網頁設置了這個屬性,後一個網頁能夠讀取它。
  2. 即在一個窗口(window)的生命週期內,窗口載入的全部的頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。

基本原理:

  1. 當在瀏覽器中打開一個頁面,或者在頁面中添加一個iframe時,即會建立一個對應的window對象,當頁面加載另外一個新的頁面時,window的name屬性是不會變的。
  2. 這樣就能夠利用在頁面動態添加一個iframe而後src加載數據頁面,在數據頁面將須要的數據賦值給window.name。
  3. 然而此時承載iframe的index頁面(也就是請求數據的頁面)仍是不能直接訪問,不在同一域下iframe的name屬性,這時只須要將iframe再加載一個與承載頁面同域的空白頁面,便可對window.name進行數據讀取

例子:(index.html通過3秒跳轉到data.html)

<script>

// 頁面index.html

window.name ='我是index.html頁面的window.name值';

setTimeout(function(){

window.location = 'http://xxx/data.html';

},3000);

</script>

// 頁面data.html,二者不一樣域名

<script>

console.log(window.name);

</script>

代碼說明:實際運行中可以看到在 data.html 頁面上成功獲取到了,它的上一個頁面 index.html 給window.name設置的值。若是在以後全部載入的頁面都沒對 window.name 進行修改的話,那麼全部這些頁面獲取到的 window.name 的值都是 index.html 頁面設置的那個值。固然,若是有須要,其中的任何一個頁面均可以對window.name的值進行修改。注意, window.name 的值只能是字符串的形式,這個字符串的大小最大能容許2M左右,具體狀況取決於不一樣的瀏覽器,但通常是夠用了。

上面的例子中,咱們用到的頁面 index.htmldata.html 在同域和不一樣域環境下都進行了測試,結果都同樣,這也正是利用window.name進行跨域的原理。

問題:這樣獲取到的 window.name 的值須要跳轉頁面獲取,天然不是咱們想要的結果,咱們想要的是頁面不跳轉也能夠獲取到數據,上面的例子是爲了體現和理解 window.name 的跨域能力,這種簡單的方法才更有利於初學者理解和學習。

實現不跳轉請求數據:接下來咱們運用 iframe+window.name 來實現,頁面不跳轉來獲取數據,方法就是:在 index.html 頁面中使用一個隱藏的 iframe 來充當一箇中間人的角色,由 iframe 去獲取 data.index 的數據,而後 index.html 再去獲得 iframe 獲取到的數據。

例子 => 三個文件:

  1. index.html是主文件,用於請求數據的主文件
  2. empty.html是空文件,是代理文件,用於過渡使用
  3. data.html是請求數據的文件

index.html和empty.html必須在同一域,data.html爲其餘域的數據文件

<script>

// index.html

var state = 0,

    iframe = document.createElement('iframe'),

    loadfn = function() {

        if (state === 1) {

            var data = iframe.contentWindow.name; // 讀取數據

            console.log(data); // 打印出'{"name":"怪誕咖啡","age":18,"job":"前端攻城獅"}',是字符串類型

        } else if (state === 0) {

            state = 1;

            iframe.contentWindow.location = "empty.html"; // 設置的代理文件

        }

    };

iframe.src = 'http://xxx/data.html';

if (iframe.attachEvent) {

    iframe.attachEvent('onload', loadfn);

} else {

    iframe.onload = loadfn;

}

document.body.appendChild(iframe);

</script>

<script>

// data.html

window.name = '{"name":"怪誕咖啡","age":18,"job":"前端攻城獅"}';

</script>

地址說明:http://xxx/data.html,其中的xxx是表示不一樣域名下的文件,測試的時候,把前端和後端代碼放到不一樣的源中測試

我在最初探索window.name+iframe的時候遇到的坑:index.htmlempty.html 沒有體如今同一域中(好比頁面地址使用 http://localhost/index.html ,裏面iframe 的跳轉地址使用 iframe.contentWindow.location = 'http://127.0.0.1/empty.html')是不能夠的,檢測爲不是同一個域,緣由請看下面介紹

localhost與127.0.0.1的區別是什麼?

localhost也叫local ,正確的解釋是:本地服務器

127.0.0.1在windows等系統的正確解釋是:本機地址(本機服務器)

他們的解析經過本機的host文件,windows自動將localhost解析爲127.0.0.1

  • localhot(local)是不經網卡傳輸!這點很重要,它不受網絡防火牆和網卡相關的的限制。
  • 127.0.0.1是經過網卡傳輸,依賴網卡,並受到網絡防火牆和網卡相關的限制。
  • 通常設置程序時本地服務用localhost是最好的,localhost不會解析成ip,也不會佔用網卡、網絡資源。
  • 有時候用localhost能夠,但用127.0.0.1就不能夠的狀況就是在於此。猜測localhost訪問時,系統帶的本機當前用戶的權限去訪問,而用ip的時候,等於本機是經過網絡再去訪問本機,可能涉及到網絡用戶的權限。

雙向跨域:兩個iframe之間或者兩個頁面之間,通常用於獲取對方數據,document.domain方式還能夠直接操做對方DOM

4、document.domain(兩個iframe之間或者相同一級域名不一樣的二級域名cookie傳遞,屬於雙向跨域)

cookie:

Cookie 是服務器寫入瀏覽器的一小段信息,只有同源的網頁才能共享。可是,兩個網頁一級域名相同,只是二級域名不一樣,瀏覽器容許經過設置document.domain共享 Cookie。

舉例來講,A網頁是 http://w1.example.com/a.html ,B網頁是 http://w2.example.com/b.html ,那麼只要設置相同的 document.domain ,兩個網頁就能夠共享Cookie。

兩個域名必須屬於同一個基礎域名!並且所用的協議,端口都要一致,不然沒法利用document.domain進行跨域

iframe:

和cookie同理,都是須要設置,document.domain

<script>

document.domain = 'example.com';

</script>

問題:一、安全性,當一個站點被攻擊後,另外一個站點會引發安全漏洞。二、若是一個頁面中引入多個iframe,要想可以操做全部iframe,必須都得設置相同domain。

5、window.postMessage(兩個iframe之間或者兩個頁面之間,屬於雙向跨域)

HTML5爲了解決這個問題,引入了一個全新的API:跨文檔通訊 API(Cross-document messaging)。

這個API爲 window 對象新增了一個 window.postMessage 方法,容許跨窗口通訊,不論這兩個窗口是否同源。

window.postMessage() 方法能夠安全地實現跨源通訊。一般,對於兩個不一樣頁面的腳本,只有當執行它們的頁面位於具備相同的協議(一般爲https),端口號(443爲https的默認值),以及主機 (兩個頁面的模數 Document.domain 設置爲相同的值) 時,這兩個腳本才能相互通訊。window.postMessage() 方法提供了一種受控機制來規避此限制,只要正確的使用,這種方法就很安全。

6、location.hash(兩個iframe之間,屬於雙向跨域),又稱FIM,Fragment identifier Messaging的簡寫

概念:片斷標識符(fragment identifier)指的是,URL的#號後面的部分,好比 http://example.com/x.html#fragment的#fragment 。若是隻是改變片斷標識符,頁面不會從新刷新。

下面是hash不刷新頁面,更新hash的例子:

setTimeout(function(){

    location.href= 'index.html' + "#" + '{"name":"怪誕咖啡","age":18,"job":"前端攻城獅"}';

},1000);

window.onhashchange = checkMessage;

 

function checkMessage() {

  var message = window.location.hash;

  console.log(message.slice(1)); //{"name":"怪誕咖啡","age":18,"job":"前端攻城獅"}

}

hash實現跨域的方式:兩個文件,一個 index.html 文件,一個是不一樣域名下的 index.php 文件

// index.html

<script>

function getData(url, fn) {

    var iframe = document.createElement('iframe');

    iframe.style.display = 'none';

    iframe.src = url;

 

    iframe.onload = function() {

        fn(iframe.contentWindow.location.hash.substring(1));

        window.location.hash = '';

        document.body.removeChild(iframe);

    };

 

    document.body.appendChild(iframe);

}

 

// get data from server

var url = 'http://127.0.0.1/index.php';

getData(url, function(data) {

    var jsondata = JSON.parse(data);

    console.log(jsondata);

});

</script>

// index.php

<?php

$data = '{\"name\":\"怪誕咖啡\",\"age\":18,\"job\":\"前端攻城獅\"}';

echo

"

<script>

window.location = 'http://localhost/index.html' + '#' + \"$data\";

</script>

"

?>

若是看懂了以前利用 window.name+iframe 跨域獲取數據,那麼使用 window.hash+iframe 也就很好理解了。同樣都是動態插入一個iframe,而後把iframe的src指向服務端地址,而服務端一樣都是輸出一段js代碼,一樣都是利用和子窗口之間的通訊完成數據傳輸,一樣要針對同源策略作出處理。

HTML標籤和跨域

看到以前的介紹,可以瞭解到跨域都是經過HTML標籤作一些事情,HTML有:script、img、iframe、link

CSS中,有偉大的 background 屬性,也能夠實現跨域

跨域的方法,能夠說不少不少,不只僅侷限於上面的方法,從此慢慢來總結這塊

相關文章
相關標籤/搜索