前端跨域的幾種方式
阿新 • • 發佈:2019-01-27
前言
受瀏覽器同源策略的限制,本域的js不能操作其他域的頁面物件(比如DOM)。但在安全限制的同時也給注入iframe或是ajax應用上帶來了不少麻煩。所以我們要通過一些方法使本域的js能夠操作其他域的頁面物件或者使其他域的js能操作本域的頁面物件(iframe之間)。
這裡需要明確的一點是:所謂的域跟js的存放伺服器沒有關係,比如baidu.com的頁面載入了google.com的js,那麼此js的所在域是baidu.com而不是google.com。也就是說,此時該js能操作baidu.com的頁面物件,而不能操作google.com的頁面物件。
跨域的方法總結
單向跨域(一般用於獲取資料)
一、使用JSONP跨域
原理:因為通過script標籤引入的js是不受同源策略的限制的(正如前文提到的baidu.com的頁面載入了google.com的js)。所以我們可以通過script標籤引入一個js或者是一個其他字尾形式(如PHP,jsp等)的檔案,此檔案返回一個js函式的呼叫,如返回JSONP_getUsers(["paco","john","lili"]),也就是說此檔案返回的結果呼叫了JSONP_getUsers函式,並且把["paco","john","lili"]傳進去,這個["paco","john","lili"]是一個使用者列表。那麼如果此時我們的頁面中有一個JSONP_getUsers函式,那麼JSONP_getUsers就被呼叫到,並且傳入了使用者列表。此時就實現了在本域獲取其他域資料的功能,也就是跨域。
實現例子如下:
前端引入遠端js並定義好JSONP_getUsers函式,注意需要先定義好JSONP_getUsers函式,避免在遠端js載入完成並呼叫JSONP_getUsers時,此函式不存在:
//本域為baidu.com
<script>
function JSONP_getUsers(users){
console.dir(users);
}
</script>
//載入google.com的getUsers.php
<script src="http://www.google.com/getUsers.php"></script>
需要google.com提供支援,getUsers.php程式碼如下:JSONP易於實現,但是也會存在一些安全隱患,如果第三方的指令碼隨意地執行,那麼它就可以篡改頁面內容,截獲敏感資料。但是在受信任的雙方傳遞資料,JSONP是非常合適的選擇。可以看出來JSONP跨域一般用於獲取其他域的資料。<?php> echo 'JSONP_getUsers(["paco","john","lili"])';//返回一個js函式的呼叫 ?>
一般能夠用JSONP實現跨域就用JSONP實現,這也是前端用的最多的跨域方法。
二、動態建立script標籤
這種方法其實是JSONP跨域的簡化版,JSONP只是在此基礎上加入了回撥函式。
比如上例中的getUsers.php返回的如果不是一個js函式的呼叫,而是一個js變數,如:
<?php>
echo 'var users=["paco","john","lili"]';//返回一個js變數users
?>
三、flash URLLoaderflash有自己的一套安全策略,伺服器可以通過crossdomain.xml檔案來宣告能被哪些域的SWF檔案訪問,SWF也可以通過API來確定自身能被哪些域的SWF載入。當跨域訪問資源時,例如從域baidu.com請求域google.com上的資料,我們可以藉助flash來發送HTTP請求。首先,修改域google.com上的crossdomain.xml(一般存放在根目錄,如果沒有需要手動建立) ,把baidu.com加入到白名單。其次,通過Flash URLLoader傳送HTTP請求,最後,通過Flash API把響應結果傳遞給JavaScript。Flash URLLoader是一種很普遍的跨域解決方案,不過需要支援iOS的話,這個方案就不可行了。
四、Access Control
此跨域方法目前只在很少的瀏覽器中得以支援,這些瀏覽器可以傳送一個跨域的HTTP請求(Firefox, Google Chrome等通過XMLHTTPRequest實現,IE8下通過XDomainRequest實現),請求的響應必須包含一個Access- Control-Allow-Origin的HTTP響應頭,該響應頭聲明瞭請求域的可訪問許可權。例如baidu.com對google.com下的getUsers.php傳送了一個跨域的HTTP請求(通過ajax),那麼getUsers.php必須加入如下的響應頭:
header("Access-Control-Allow-Origin: http://www.baidu.com");//表示允許baidu.com跨域請求本檔案
try {
parent.location.hash = 'data';
} catch (e) {
// ie、chrome的安全機制無法修改parent.location.hash,
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = "http://www.baidu.com/proxy.html#data";
document.body.appendChild(ifrproxy);
}
proxy.html頁面的關鍵程式碼如下: //因為parent.parent(即baidu.com/a.html)和baidu.com/proxy.html屬於同一個域,所以可以改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
九、使用HTML5的postMessage方法(兩個iframe之間或者兩個頁面之間)高階瀏覽器Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都將支援這個功能。這個功能主要包括接受資訊的"message"事件和傳送訊息的"postMessage"方法。比如baidu.com域的A頁面通過iframe嵌入了一個google.com域的B頁面,可以通過以下方法實現A和B的通訊
A頁面通過postMessage方法傳送訊息:
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = "http://www.google.com";
ifr.contentWindow.postMessage('hello world!', targetOrigin);
};
postMessage的使用方法:otherWindow.postMessage(message, targetOrigin);
otherWindow: 指目標視窗,也就是給哪個window發訊息,是 window.frames 屬性的成員或者由 window.open 方法建立的視窗
message: 是要傳送的訊息,型別為 String、Object (IE8、9 不支援)
targetOrigin: 是限定訊息接收範圍,不限制請使用 '*'
B頁面通過message事件監聽並接受訊息:
var onmessage = function (event) {
var data = event.data;//訊息
var origin = event.origin;//訊息來源地址
var source = event.source;//源Window物件
if(origin=="http://www.baidu.com"){
console.log(data);//hello world!
}
};
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
//for ie
window.attachEvent('onmessage', onmessage);
}
同理,也可以B頁面傳送訊息,然後A頁面監聽並接受訊息。
總結
跨域的方法很多,不同的應用場景我們都可以找到一個最合適的解決方案。比如單向的資料請求,我們應該優先選擇JSONP或者window.name,雙向通訊優先採取location.hash,在未與資料提供方達成通訊協議的情況下我們也可以用server proxy的方式來抓取資料。