摘要:跨域問題,不管是面試仍是平時的工做中,都會遇到,本文總結處理跨域問題的幾種方法以及其原理,也讓本身搞懂這方面的知識,走起。javascript
在JavaScript中,有一個很重要的安全性限制,被稱爲「Same-Origin Policy」(同源策略)。這一策略對於JavaScript代碼可以訪問的頁面內容作了很重要的限制,即JavaScript只能訪問與包含它的文檔在同一域下的內容。php
JavaScript這個安全策略在進行多iframe或多窗口編程、以及Ajax編程時顯得尤其重要。根據這個策略,在baidu.com下的頁面中包含的JavaScript代碼,不能訪問在google.com域名下的頁面內容;甚至不一樣的子域名之間的頁面也不能經過JavaScript代碼互相訪問。對於Ajax的影響在於,經過XMLHttpRequest實現的Ajax請求,不能向不一樣的域提交請求,例如,在abc.example.com下的頁面,不能向def.example.com提交Ajax請求,等等。css
爲何瀏覽器要實現同源限制?咱們舉例說明:html
好比一個黑客,他利用iframe把真正的銀行登陸頁面嵌到他的頁面上,當你使用真實的用戶名和密碼登陸時,若是沒有同源限制,他的頁面就能夠經過javascript讀取到你的表單中輸入的內容,這樣用戶名和密碼就輕鬆到手了.前端
又好比你登陸了OSC,同時瀏覽了惡意網站,若是沒有同源限制,該惡意 網站就能夠構造AJAX請求頻繁在OSC發廣告帖.html5
跨域的狀況分爲如下幾種:java
特別注意兩點:jquery
一、若是是協議和端口形成的跨域問題「前臺」是無能爲力的面試
二、在跨域問題上,域僅僅是經過「URL的首部」來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。好比上面的,http://www.a.com/a.js和http://70.32.92.74/b.js。雖然域名和域名的ip對應,不過仍是被認爲是跨域。ajax
「URL的首部」指window.location.protocol +window.location.host。其中,
window.location.protocol:指含有URL第一部分的字符串,如http:
window.location.host:指包含有URL中主機名:端口號部分的字符串.如//www.cenpok.net/server/
一、JSONP
二、CORS策略
三、document.domain+iframe的設置
四、HTML5的postMessage
五、使用window.name來進行跨域
原理 :
咱們知道,在頁面上有三種資源是能夠與頁面自己不一樣源的。它們是:js腳本,css樣式文件,圖片,像淘寶等大型網站,確定會將這些靜態資源放入cdn中,而後在頁面上連
接,以下所示,因此它們是能夠連接訪問到不一樣源的資源的。
1)<script type="text/javascript" src="某某cdn地址" ></script>
2)<link type="text/css" rel="stylesheet" href="某個cdn地址" />
3)<img src="某個cdn地址" alt=""/>
而jsonp就是利用了script標籤的src屬性是沒有跨域的限制的,從而達到跨域訪問的目的。所以它的最基本原理就是:動態添加一個<script>標籤來實現。
實現方法:
這裏是使用ajax來請求的,看起來和ajax沒啥區別,其實仍是有區別的。
ajax的核心是經過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加<script>標籤來調用服務器提供的js腳本。
$.ajax({ url:"http://crossdomain.com/services.php", dataType:'jsonp', data:'', jsonp:'callback', success:function(result) { // some code
} });
上面的代碼中,callback是必須的,callback是什麼值要跟後臺拿。獲取到的jsonp數據格式以下:
flightHandler({ "code": "CA1998", "price": 1780, "tickets": 5 });
jsonp的全稱爲json with padding,上面的數據中,flightHandler就是那個padding.
JSONP的不足之處:
一、只能使用get方法,不能使用post方法:
咱們知道 script,link, img 等等標籤引入外部資源,都是 get 請求的,那麼就決定了 jsonp 必定是 get 的。但有時候咱們使用的 post 請求也成功,爲啥呢?這是由於當咱們指定dataType:'jsonp',不論你指定:type:"post" 或者type:"get",其實質上進行的都是 get 請求!
二、沒有關於 JSONP 調用的錯誤處理。若是動態腳本插入有效,就執行調用;若是無效,就靜默失敗。失敗是沒有任何提示的。例如,不能從服務器捕捉到 404 錯誤,也不能取消或從新開始請求。不過,等待一段時間尚未響應的話,就不用理它了。
原理:
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它容許瀏覽器向跨源服務器,發出XMLHttpRequest
請求,從而克服了AJAX只能同源使用的限制。它爲Web服務器定義了一種方式,容許網頁從不一樣的域訪問其資源.
CORS系統定義了一種瀏覽器和服務器交互的方式來肯定是否容許跨域請求。 它是一個妥協,有更大的靈活性,但比起簡單地容許全部這些的要求來講更加安全。
實現方法:
CORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。
整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。
前端方面
之前咱們使用Ajax,代碼相似於以下的方式:
var xhr = new XMLHttpRequest(); xhr.open("GET", "/hfahe", true); xhr.send(); // 這裏的「/hfahe」是本域的相對路徑。
若是咱們要使用CORS,相關Ajax代碼可能以下所示:
var xhr = new XMLHttpRequest(); xhr.open("GET", "http://blog.csdn.net/hfahe", true); xhr.send(); // 請注意,代碼與以前的區別就在於相對路徑換成了其餘域的絕對路徑,也就是你要跨域訪問的接口地址。
服務器方面
服務器端對於CORS的支持,主要就是經過設置Access-Control-Allow-Origin來進行的。若是瀏覽器檢測到相應的設置,就能夠容許Ajax進行跨域的訪問。
CORS策略的優缺點:
優勢:
一、CORS支持全部類型的HTTP請求。
二、 使用CORS,開發者可使用普通的XMLHttpRequest發起請求和得到數據,比起JSONP有更好的錯誤處理。
缺點: 兼容性方面相對差一點,ie10或以上才支持
瀏覽器中不一樣域的框架之間是不能進行js的交互操做的。可是不一樣的框架之間(父子或同輩),是可以獲取到彼此的window對象的,可是,咱們也只能獲取到一個幾乎
無用的window對象。好比,有一個頁面,它的地址是 http://www.example.com/a.html , 在這個頁面裏面有一個iframe,它的src是 http://example.com/b.html , 很顯然,這
個頁面與它裏面的iframe框架是不一樣域的,因此咱們是沒法經過在頁面中書寫js代碼來獲取iframe中的東西的。
這個時候,document.domain就能夠派上用場了,咱們只要把 http://www.example.com/a.html 和 http://example.com/b.html 這兩個頁面的document.domain都設成
相同的域名就能夠了。但要注意的是,document.domain的設置是有限制的,咱們只能把document.domain設置成自身或更高一級的父域,且主域必須相同。例如:
a.b.example.com 中某個文檔的document.domain 能夠設成a.b.example.com、b.example.com 、example.com中的任意一個,可是不能夠設成 c.a.b.example.com,由於這是
當前域的子域,也不能夠設成baidu.com,由於主域已經不相同了。
使用方法:
好比在http://www.example.com/a.html 的頁面裏要訪問 http://example.com/b.html裏面的東西。
在頁面 http://www.example.com/a.html 中設置document.domain:
//http://www.example.com/a.html <html> <head> <title>A頁面</title> <script type="text/javascript" src="jquery.js"></script> </head> <body> <div>A頁面</div> <iframe id="iframe" src="http://example.com/b.html" style="display:none;"></iframe>
// 至關於用一個隱藏的iframe來作代理 <script> $(function(){ try{ document.domain = "example.com"; //這裏將document.domain設置成同樣 }catch(e){} $("#iframe").load(function(){ var iframe = $("#iframe").contentDocument.$; ifram.get("http://example.com/接口",function(data){}); }); }); </script> <body> </html>
在頁面 http://example.com/b.html 中也設置document.domain,並且這也是必須的,雖然這個文檔的domain就是example.com,可是仍是必須顯示的設置document.domain的值:
//http://example.com/b.html
<html>
<head>
<title>B頁面</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<div>B頁面</div>
<script> $(function(){ try{ document.domain = "example.com"; //這裏將document.domain設置成同樣 }catch(e){} }); </script>
</body>
</html>
這裏有個注意點,就是在A頁面中,要等iframe標籤完成加載B頁面以後,再取iframe對象的contentDocument,不然若是B頁面沒有被iframe徹底加載,在A頁面中經過contentDocument屬性就取不到B頁面中的jQuery對象。
一旦取到B頁面中的jQuery對象,就能夠直接發ajax請求了,這種相似「代理」方式能夠解決主子域的跨域問題。
缺點:
只有在主域相同的時候才能使用該方法
原理:
沒啥原理,就是一個html5所提供的一個API.--->HTML5 window.postMessage是一個安全的、基於事件的消息API。
在須要發送消息的源窗口調用postMessage方法便可發送消息。其中.源窗口能夠是全局的window對象,也能夠是如下類型的窗口:
一、文檔窗口中的iframe:
var iframe = document.getElementById('my-iframe'); var win = iframe.documentWindow;
二、JavaScript打開的彈窗:
var win = window.open();
三、當前文檔窗口的父窗口:
var win = window.parent;
四、
var win = window.opener();
發送消息:找到源window對象後,便可調用postMessage API向目標窗口發送消息:
win.postMessage(msg, targetOrigin);
說明:postMessage函數接收兩個參數:
一、msg, 將要發送的消息,可使一切javascript參數,如字符串,數字,對象,數組等。
二、targetOrigin,這個參數稱做「目標域」,注意,是目標域不是本域!好比,你想在2.com的網頁上往1.com網頁上傳消息,那麼這個參數就是「http://1.com/」,而不是2.com.協議,(一個完整的域名包括:主機名,端口號。如:http://g.cn:80/)
接收消息:那目標窗口要怎麼接收傳過來的數據呢,只要監聽window的message事件就能夠接收了。
var onmessage = function (event) { var data = event.data; var origin = event.origin; //do someing
}; if (typeof window.addEventListener != 'undefined') { window.addEventListener('message', onmessage, false); } else if (typeof window.attachEvent != 'undefined') { //for ie
window.attachEvent('onmessage', onmessage); }
message事件監聽函數接收一個參數,Event對象實例,該對象有三個屬性:
使用方法(案例):
http://test.com/index.html--> 發送消息的頁面
<!-- 這個是 http://test.com/index.html 頁面 -->
<div>
<!-- 要給下面的頁面傳一個妹子過去 -->
<iframe id="child" src="http://lsLib.com/lsLib.html"></iframe>
</div>
<script type="text/javascript"> window.onload=function(){ window.frames[0].postMessage('蒼老師','http://lslib.com'); } </script>
http://lslib.com/lslib.html --> 接收消息的頁面
<!-- 這個是 http://lslib.com/lslib.html 頁面 -->
<script type="text/javascript"> window.addEventListener('message',function (e) { console.log(e.origin,e.data); alert('收到妹子一枚:'+e.data); }); </script>
優缺點:
優勢:方便,安全,有效的解決了跨域問題
缺點:萬惡的資本主義,ie8+才支持,並且ie8+<ie10只支持iframe的方式。
原理:
當iframe的頁面跳到其餘地址時,其window.name值保持不變,而且能夠支持很是長的 name 值(2MB)。
瀏覽器跨域iframe禁止互相調用/傳值.可是調用iframe時 window.name 卻不變,正是利用這個特性來互相傳值,固然跨域下是不允許讀取ifram的window.name值.
因此這裏咱們還要準備一個和主頁面http://www.a.com/main.html 相同域下的代理頁面http://www.a.com/other.html ,iframe調用子頁面 http://www.b.com/data.html
使用方法:
一、 準備三個頁面:
http://www.a.com/main.html //應用頁面
http://www.a.com/other.html // 代理頁面,要求和應用頁面在同一個域。通常是一個空的html
http://www.b.com/data.html //應用頁面獲取數據的頁面,簡稱:數據頁面
二、
數據頁面將數據傳到window.name中去。以下:
http://www.b.com/data.html中的 data.html
// data.html
window.name="蒼老師"; //能夠是其餘類型的數據,好比數組,對象等等
http://www.a.com/main.html //應用頁面的代碼以下:
<!-- main.html -->
var iframeData; var state = 0;//開關變量
var iframe = document.createElement('iframe'); //建立iframe
var loadfn = function() { if (state === 1) { iframeData = iframe.contentWindow.name; // 讀取數據
alert('獲取到了iframe傳過來的妹子'+iframeData); }else if (state === 0) { state = 1; iframe.contentWindow.location = 'http://www.a.com/other.html'; //這裏是代理頁面 other.html
/** 這裏說明一下: 因爲iframe的location改變了,至關於從新載入頁面(這是iframe的性質決定的),因而從新執行loadfn方法。
因爲當iframe的頁面跳到其餘地址時,其window.name值保持不變,而且此時開關變量 state已經變爲1,
因而就能夠獲取到window.name值,也就達到了跨域訪問的目的了。 **/ }; } iframe.src = 'http://www.b.com/data.html'; //這是是數據頁面,data.html
if (iframe.attachEvent) { iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe);
三、獲取數據之後銷燬這個iframe,釋放內存;這也保證了安全(不被其餘域frame js訪問)。
iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe);
優缺點:
瀏覽器支持狀況好,是比較廣泛的使用方法
以上總結了js跨域的幾種方法,固然還有其餘的方法,不過沒有。他們各有千秋。其實最主要的區別除了實現方式不同,主要是瀏覽器的兼容問題而已。
JSONP:
JSONP的優勢是:它不像XMLHttpRequest
對象實現的Ajax請求那樣受到同源策略的限制;它的兼容性更好,在更加古老的瀏覽器中均可以運行,不須要XMLHttpRequest或ActiveX的支持;而且在請求完畢後能夠經過調用callback的方式回傳結果。
JSONP的缺點則是:它只支持GET請求而不支持POST等其它類型的HTTP請求;它只支持跨域HTTP請求這種狀況,不能解決不一樣域的兩個頁面之間如何進行JavaScript
調用的問題。
CORS策略
優勢:使用CORS,開發者可使用普通的XMLHttpRequest發起請求和得到數據,比起JSONP有更好的錯誤處理。
缺點:古老的瀏覽器不支持,不過大部分現代瀏覽器都支持
document.domain+iframe:只適用於主域相同的跨域問題處理
html5的postMessage:
優勢:是html5
新引進的特性,可使用它來向其它的window
對象發送消息,不管這個window對象是屬於同源或不一樣源,目前IE8+、FireFox、Chrome、Opera
等瀏覽器都已經支持window.postMessage
方法。若是是現代瀏覽器,首選。
缺點: ie8之前不支持
window.name:
主要是應用當frame的頁面跳到其餘地址時,其window.name值保持不變的原理。兼容性好。須要照顧落後的瀏覽器時,首選。、
小小總結,有誤之處,歡迎指出