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標籤引入的文件不受同源策略的限制?
JSONP的缺點則是:
地址說明:http://xxx/getPerson.php?name=Hello&age=18,其中的xxx是表示不一樣域名下的文件,測試的時候,把前端和後端代碼放到不一樣的源中測試
感悟:
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屬性(單項跨域-通常用於獲取數據)
來源:
特徵:
基本原理:
例子:(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.html 和 data.html 在同域和不一樣域環境下都進行了測試,結果都同樣,這也正是利用window.name進行跨域的原理。
問題:這樣獲取到的 window.name 的值須要跳轉頁面獲取,天然不是咱們想要的結果,咱們想要的是頁面不跳轉也能夠獲取到數據,上面的例子是爲了體現和理解 window.name 的跨域能力,這種簡單的方法才更有利於初學者理解和學習。
實現不跳轉請求數據:接下來咱們運用 iframe+window.name 來實現,頁面不跳轉來獲取數據,方法就是:在 index.html 頁面中使用一個隱藏的 iframe 來充當一箇中間人的角色,由 iframe 去獲取 data.index 的數據,而後 index.html 再去獲得 iframe 獲取到的數據。
例子 => 三個文件:
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.html 和 empty.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
雙向跨域:兩個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 屬性,也能夠實現跨域
跨域的方法,能夠說不少不少,不只僅侷限於上面的方法,從此慢慢來總結這塊