1. 程式人生 > >JSON跨域解決方案收集

JSON跨域解決方案收集

get ogl obb 本地 allow con mesa 跨域請求 loader

最近面試問的挺多的一個問題,就是JavaScript的跨域問題。在這裏,對跨域的一些方法做個總結。由於瀏覽器的同源策略,不同域名、不同端口、不同協議都會構成跨域;但在實際的業務中,很多場景需要進行跨域傳遞信息,這樣就催生出多種跨域方法。

1. 具備src的標簽

  • 原理:所有具有 src 屬性的HTML標簽都是可以跨域的

在瀏覽器中, <script> 、 <img> 、 <iframe> 和 <link> 這幾個標簽是可以加載跨域(非同源)的資源的,並且加載的方式其實相當於一次普通的GET請求,唯一不同的是,為了安全起見,瀏覽器不允許這種方式下對加載到的資源的讀寫操作,而只能使用標簽本身應當具備的能力(比如腳本執行、樣式應用等等)。

2. JSONP跨域

  • 原理: <script> 是可以跨域的,而且在跨域腳本中可以直接回調當前腳本的函數

script標簽是可以加載異域的JavaScript並執行的,通過預先設定好的callback函數來實現和母頁面的交互。它有一個大名,叫做JSONP跨域,JSONP是JSON with Padding的略稱。它是一個非官方的協議,明明是加載script,為啥和JSON扯上關系呢?原來就是這個callback函數,對它的使用有一個典型的方式,就是通過JSON來傳參,即將JSON數據填充進回調函數,這就是JSONP的JSON+Padding的含義。JSONP只支持GET請求。

前端代碼:

<script type="text/javascript">
 function dosomething(jsondata){
 //處理獲得的json數據
 }
</script>
<script src="http://haorooms.com/data.php?callback=dosomething"></script>

後臺代碼:

<?php
$callback = $_GET[‘callback‘];//得到回調函數名
$data = array(‘a‘,‘b‘,‘c‘);//要返回的數據
echo $callback.‘(‘.json_encode($data).‘)‘;//輸出
?>

3. 跨域資源共享(CORS)

  • 原理:服務器設置Access-Control-Allow-Origin HTTP響應頭之後,瀏覽器將會允許跨域請求

CORS是HTML5標準提出的跨域資源共享(Cross Origin Resource Share),支持GET、POST等所有HTTP請求。CORS需要服務器端設置 Access-Control-Allow-Origin 頭,否則瀏覽器會因為安全策略攔截返回的信息。

Access-Control-Allow-Origin: *              # 允許所有域名訪問,或者
Access-Control-Allow-Origin: http://a.com   # 只允許所有域名訪問

CORS又分為簡單跨域和非簡單跨域請求,有關CORS的詳細介紹請看 阮一峰 的 跨域資源共享 CORS 詳解 ,裏面講解的非常詳細。

4. document.domain

  • 原理:相同主域名不同子域名下的頁面,可以設置document.domain讓它們同域

我們只需要在跨域的兩個頁面中設置document.domain就可以了。修改document.domain的方法只適用於不同子域的框架間的交互,要載入iframe頁面。

例如:1. 在頁面 http://a.example.com/a.html 設置document.domain

<iframe id = "iframe" src="http://b.example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
 document.domain = ‘example.com‘;//設置成主域
 function test(){
 alert(document.getElementById(‘?iframe‘).contentWindow);//contentWindow 可取得子窗口的 window 對象
 }
</script>

2、在頁面http:// b.example.com/b.html 中設置document.domain

<script type="text/javascript">
 document.domain = ‘example.com‘;//在iframe載入這個頁面也設置document.domain,使之與主頁面的document.domain相同
</script>

5. window.name

  • 原理:window對象有個name屬性,該屬性有個特征:即在一個窗口(window)的生命周期內,窗口載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的所有頁面中的。

這裏有三個頁面:

  • sever.com/a.html 數據存放頁面

  • agent.com/b.html 數據獲取頁面

  • agent.com/c.html 空頁面,做代理使用

a.html中,設定 window.name 作為需要傳遞的值

<script>
 window.name = ‘I was there!‘;
 alert(window.name);
</script>

b.html中,當iframe加載後將iframe的 src 指向同域的 c.html ,這樣就可以利用 iframe.contentWindow.name 獲取要傳遞的值了

<body>
  <script type="text/javascript">
 iframe = document.createElement(‘iframe‘);
 iframe.style.display = ‘none‘;
 var state = 0;
 iframe.onload = function() {
 if(state === 1) {
 var data = JSON.parse(iframe.contentWindow.name);
 alert(data);
 iframe.contentWindow.document.write(‘‘);
 iframe.contentWindow.close();
 document.body.removeChild(iframe);
 } else if(state === 0) {
 state = 1;
 iframe.contentWindow.location = ‘http://agent.com/c.html‘;
 }
 };
 iframe.src = ‘http://sever.com/a.html‘;
 document.body.appendChild(iframe);
 </script>
</body>

成功獲取跨域數據,效果如下:

技術分享

6. window.postMesage

  • 原理: HTML5新增的postMessage方法,通過postMessage來傳遞信息,對方可以通過監聽message事件來監聽信息。可跨主域名及雙向跨域。

這裏有兩個頁面:

  1. agent.com/index.html

  2. server.com/remote.html

本地代碼index.html

<body>  
    <iframe id="proxy" src="http://server.com/remote.html" onload = "postMsg()" style="display: none" ></iframe>  
    <script type="text/javascript"> 
 var obj = { 
 msg: ‘hello world‘ 
 } 
 function postMsg (){ 
 var iframe = document.getElementById(‘proxy‘); 
 var win = iframe.contentWindow; 
 win.postMessage(obj,‘http://server.com‘); 
 } 
 </script>  
</body>

postMessage 的使用方法: otherWindow.postMessage(message, targetOrigin);

  • otherWindow: 指目標窗口,也就是給哪個window發消息,是 window.frames 屬性的成員或者由 window.open 方法創建的窗口

  • message: 是要發送的消息,類型為 String、Object (IE8、9 不支持)

  • targetOrigin: 是限定消息接收範圍,不限制請使用 ‘*’

server.com上remote.html,監聽 message 事件,並檢查來源是否是要通信的域。

<head>
    <title></title>
    <script type="text/javascript">
        window.onmessage = function(e){
            if(e.origin !== ‘http://localhost:8088‘) return;
            alert(e.data.msg+" from "+e.origin);
        }
    </script>
</head>

技術分享

7. location.hash

原理:

  • 這個辦法比較繞,但是可以解決完全跨域情況下的腳步置換問題。原理是利用location.hash來進行傳值。www.a.com下的a.html想和www.b.com下的b.html通信(在a.html中動態創建一個b.html的iframe來發送請求)

  • 但是由於“同源策略”的限制他們無法進行交流(b.html無法返回數據),於是就找個中間人:www.a.com下的c.html(註意是www.a.com下的)。

  • b.html將數據傳給c.html(b.html中創建c.html的iframe),由於c.html和a.html同源,於是可通過c.html將返回的數據傳回給a.html,從而達到跨域的效果。

技術分享

a.html代碼如下:

<script>
function startRequest(){ 
 var ifr = document.createElement(‘iframe‘); 
 ifr.style.display = ‘none‘; 
 ifr.src = ‘http://www.b.com/b.html#sayHi‘; //傳遞的location.hash 
 document.body.appendChild(ifr); 
} 
function checkHash() { 
 try { 
 var data = location.hash ? location.hash.substring(1) : ‘‘; 
 if (console.log) { 
 console.log(‘Now the data is ‘+data); 
 } 
 } catch(e) {}; 
} 
setInterval(checkHash, 2000); 
window.onload = startRequest;
</script>

b.html代碼如下:

<script>
function checkHash(){
 var data = ‘‘;
 //模擬一個簡單的參數處理操作
 switch(location.hash){
 case ‘#sayHello‘: data = ‘HelloWorld‘;break;
 case ‘#sayHi‘: data = ‘HiWorld‘;break;
 default: break;
 }
 data && callBack(‘#‘+data);
}
function callBack(hash){
 // ie、chrome的安全機制無法修改parent.location.hash,所以要利用一個中間的www.a.com域下的代理iframe
 var proxy = document.createElement(‘iframe‘);
 proxy.style.display = ‘none‘;
 proxy.src = ‘http://localhost:8088/proxy.html‘+hash; // 註意該文件在"www.a.com"域下
 document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>

由於兩個頁面不在同一個域下,IE、Chrome不允許修改parent.location.hash的值,所以要借助於a.com域名下的一個代理iframe,這裏有一個a.com下的代理文件c.html。Firefox可以修改。

c.html代碼如下:

<script>parent.parent.location.hash = self.location.hash.substring(1); </script>

直接訪問a.html,a.html向b.html發送的消息為”sayHi”;b.html通過消息判斷返回了”HiWorld”,並通過c.html改變了location.hash的值

技術分享

8. flash URLLoader

flash有自己的一套安全策略,服務器可以通過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的話,這個方案就不可行了。

小結

總的來說,常見的跨域方法如上述。在不同的業務場景下,各有適合的跨域方式。跨域解決了一些資源共享、信息交互的難題,但是有的跨域方式可能會帶來安全問題,如jsonp可導致水坑攻擊,等標簽會被用來進行xss或csrf攻擊。所以,在應用跨域的場景,需要格外註意安全問題。

公眾號原文

JSON跨域解決方案收集