標籤:.com 等等 伺服器 能力 localhost elf 難題 不同 res
最近面試問的挺多的一個問題,就是JavaScript的跨域問題。在這裡,對跨域的一些方法做個總結。由於瀏覽器的同源策略,不同網域名稱、不同連接埠、不同協議都會構成跨域;但在實際的業務中,很多情境需要進行跨域傳遞資訊,這樣就催生出多種跨域方法。
1. 具備src的標籤
在瀏覽器中, <script> 、 <img> 、 <iframe> 和 <link> 這幾個標籤是可以載入跨域(非同源)的資源的,並且載入的方式其實相當於一次普通的GET請求,唯一不同的是,為了安全起見,瀏覽器不允許這種方式下對載入到的資源的讀寫操作,而只能使用標籤本身應當具備的能力(比如指令碼執行、樣式應用等等)。
2. JSONP跨域
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)
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的方法只適用於不同子域的架構間的互動,要載入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
這裡有三個頁面:
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
這裡有兩個頁面:
agent.com/index.html
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跨域解決方案收集