這一節,我們來講一講,前端跨域的那些事,主要分成這樣的幾部分來講解,
一、為什么要跨域?
二、常見的幾種跨域與使用場景
三、總結
跨域,通常情況下是說在兩個不通過的域名下面無法進行正常的通信,或者說是無法獲取其他域名下面的數據,這個主要的原因是,瀏覽器出於安全問題的考慮,采用了同源策略,通過瀏覽器對JS的限制,防止惡意用戶獲取非法的數據。比如這樣的一個場景,惡意用戶仿造一個銀行的官網,在用戶輸入框中嵌套了銀行的頁面,如果是沒有同源策略的限制,那么惡意用戶則可以通過這樣的一種方法來獲取銀行用戶的卡號和登錄密碼,這樣對於瀏覽器來說是沒有安全性可言的。同時也可以有效的規避了大部分的XSS攻擊(XSS攻擊原理:通過向用戶界面中注入script腳本,然后在腳本中獲取用戶的token等身份信息,然后將身份信息發送到惡意用戶指定的地方,在正常用戶還沒有推出的時候,也就是token等身份信息還有效的時候,通過這些信息強制登錄,將正常用戶擠下系統。)
前端的跨域主要有:JSONP跨域、iframe跨域、window.name 跨域、document.domain 跨域、cookie跨域、postMessage跨域 后端的跨域:http配置
這里我們就主要說明一下前端的跨域,后端的跨域方法不做說明:
跨域方式 | 特點 | 局限 |
---|---|---|
JSONP跨域 | 1、JSONP跨域不是一種前端技術,而是程序猿創造的一種跨域方法 2、JSONP跨域,是一種簡單的跨域方法,兼容性比較好 |
|
iframe 跨域 | 1、操作簡便 2、兼容性好 | 單純的使用iframe跨域無法獲取其他域名下的數據 |
window.name 跨域 | 1、必須與iframe配合使用 2、可以獲取其他域名下的數據 | |
document.domain 跨域 | 1、必須保證兩個要跨域的對象是有一個關聯域名 | 1、只針對兩個跨域對象是關聯域名 2、如果一個域名被攻擊,那么另外一個域名也有安全問題 |
cookie跨域 | 1、這種方法跨域的兼容性好,由於需要一些額外的設置,所以刪除cookie的時候比較繁瑣 | 必須保證兩個域名為關聯域名 |
postMessage跨域 | 1、這種方法可以直接實現將數據從A站點傳輸到B站點,而且解除了cookie的限制和JSONP無法獲取要傳入的站點的信息 | 這個為HTML5新增加的特性,瀏覽器的兼容性不是很好,低版本的瀏覽器不支持,具體可以見:CANIUSE網站 |
JSONP跨域主要的依據是利用一些HTML標簽的“漏洞”,然后通過跨域的方式去調用這個在別的域名下面的腳本文件,JSONP跨域有script跨域
我們先來一個簡單的例子,我們先下載一個phpStudy,然后配置兩個本地服務器,分別為:www.test1.com、www.test2.com
在www.test1.com域名下面我們添加一個test.js文件,內容如下:
alert("test!");
然后在www.test2.com域名下面我們添加一個HTML文件,如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script src="http://www.test1.com/test.js" /> </body> </html>
這個相信大多數人都是見過的,可能你會說這個不就是簡單的script腳本的引入嗎?對的,就是因為script支持跨域,這個也就是我們常見的CDN。但是上面的例子只能說是一個簡單的JSONP跨域的應用而已,跨域的目的是要實現數據的傳輸。我們可以按照這個思路這樣去改寫:
首先我們可以在一個域名下面書寫一個方法,然后通過在外部加載一個腳本來調用這個方法,向這個方法中去傳值,這樣就可以實現把外部的數據傳到當前的域名的下面,從而實現了跨域。
我們在test.js文件下面定義一個
test("tthis is js load script");
www.test2.com下面的HTML文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascript"> var test=function(data){ alert(data); } </script> <script type="text/javascript" src="DEMO.js"></script> </body> </html>
這個我們可以理解為是在一個HMTL文件中書寫一個test方法,然后test方法允許調用的時候向里面傳參,之后通過外部加載一個JS文件,JS文件實現的邏輯是調用這個方法,所以我們不能夠把定義的test方法放在調用的DEMO.js的文件后面,因為如果我們這樣做了的話,那么在調用外部的JS文件的時候,test方法沒有加載進來所以我們便看不到效果所在,這個有興趣的同學可以自己去試驗一下,這里就不做試驗了。
在實際的應用中,這樣的例子是基本上不可能看到的,因為這樣的參數傳遞太過於死板,應用性不強,我們在實際的應用中應該要達到的效果是可以根據我們的需求做出相應的響應,這樣說吧,大家可能都知道或者調用過接口吧,接口其實我們可以理解為就是JSONP的一種應用,接下來我們就來試驗一下:
說了這么多,大家應該還是對具體怎樣使用有點雲里霧里的吧,現在我們就基於JSONP的思想來制作一個簡易的音樂專輯查詢器
首先我們應該要找到一個可以查詢專輯的公共接口
http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w=關鍵字&perpage=1&ie=utf-8
然后我們就編寫如下的HTML代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JSONP2</title> </head> <body> <input type="text" id="song" name=""> <input type="button" id="song_search" value="歌曲搜索" name=""> <br /> <div style="width:200px;height:200px;background:pink" id="song_list"></div> <script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.min.js"></script> <script type="text/javascript"> var searchJsonCallback=function(data){ //遍歷查詢結果 var alb_html=''; for(var i in data.list){ alb_html+='<span>專輯:</span><div style="color:black">'+data.list[0].albumname+'</div>'; } $("#song_list").html(alb_html); } $("#song_search").on("click",function(){ var keyword=$("#song").val(); if(keyword==undefined||keyword==""){ alert("歌曲搜索不能為空"); return false; }else{ var url = "http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w="+keyword+"&perpage=1&ie=utf-8"; // 創建script標簽,設置其屬性 var script = document.createElement('script'); script.setAttribute('src', url); // 把script標簽加入head,此時調用開始 document.getElementsByTagName('head')[0].appendChild(script); } }) </script> </body> </html>
效果如下:
這個就是JSOP在實際中的應用,如果直接用ajax來實現的話,原理也是一樣,只不過要把script標簽換成ajax調用而已。
如果你要全部使用jquery來實現的話,那么只需要把ajax中的dataType類型換成jsonp即可(通過上面的例子我們知道script加載實際上也是一個get請求,這個可以在network中驗證),這個有興趣的同學可以查一查資料。
iframe跨域的原理跟script跨域一樣,但是我們要注意的是標簽自身功能的差異性,具體差異如下:
1、script單純就是引入的作用,但是iframe標簽還有一個作用是顯示的作用可以把遠程加載的HTML代碼顯示出來,也就是script無法引入HTML代碼文件
2、script標簽只能夠從遠程獲取數據,無法操作遠程文件執行。但是iframe可以這樣
上面的第二點說起來有點難理解,我們就通過一個例子來說明一下:
假設有這樣的一個需求我們需要在www.jsonp1.com下面調用一個方法來清除josnp2.com下面的本地存儲,
首先我們在jsonp1.com下面的index.html中加入script標簽去調用jsonp2.com下面的js文件
具體如下:
window.localStorage.clear();
alert(1);
結果是:
我們可以看到內容為1的彈窗,但是在jsonp2的本地存儲就沒有被清除,所以我們可以得出結論,script標簽的跨域適用於從遠程獲取數據,不適用對遠程的操作。
這個需求我們可以使用iframe標簽來實現:
jsonp1.com中的HTML文件如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>jsonp1</title> </head> <body> <iframe src="http://www.jsonp2.com/demo.html"></iframe> </body> </html>
jsonp2下面的demo.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascript"> window.localStorage.clear(); alert("清除成功"); </script> </body> </html>
這樣我們就實現了通過在JSONP1中調用JSONP2中的文件來清除JSONP2中的本地存儲
但是大家想一想,我們除了使用這種方法來控制遠程操作之外,我們還可以像script跨域一樣來獲取本地存儲的數據嗎?
答案是理論上是不可以實現的(本例子為博主的思路的構思,沒有具體實踐過,如有錯誤望各位指出),如圖所示:
首先我們就在jsonp1.com網站下面的index.html文件下面通過iframe插入jsonp2.com/index.html,這樣我們就使用了iframe跨域,但是由於同源策略的限制,所以無法將jsonp2.com/index.html的值回傳給jsonp1.com/index.html,所以這個時候在jsonp2.com/index.html是可以獲取到站點的本地存儲的,我們就可以像上面音樂接口去使用,把本地儲存中的數值,傳遞過去,但是這個時候jsonp1.com/index.html文件是無法直接獲取接口中返回的東西的,也無法通過jsonp2.com/index.html回調,所以這種方法是不可行的
但是我們就真的無法實現這樣一個獲取遠程的站點的本地存儲的功能嗎?不是的這個時候利用window.name方法結合iframe來實現跨域
www.jsonp1.com下面的index.html代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>跨域獲取數據</title> <script type="text/javascript"> function domainData(url, fn) { var isFirst = true; var iframe = document.createElement('iframe'); iframe.style.width=0; iframe.style.height=0; iframe.style.display = 'none'; var loadfn = function(){ if(isFirst){ iframe.contentWindow.location = 'http://www.jsonp1.com/proxy.html'; isFirst = false; } else { //alert(1); //console.log(iframe.contentWindow.name); alert(iframe.contentWindow.name); iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); iframe.src = ''; iframe = null; } }; iframe.src = url; if(iframe.attachEvent){ iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe); } </script> </head> <body> </body> <script type="text/javascript"> domainData('http://www.jsonp2.com/demo.html', function(data){ alert(data); }); </script> </html>
www.jsonp2.com/demo.html代碼如下:
<script type="text/javascript"> window.localStorage.setItem("test","123"); var data=window.localStorage.getItem("test"); window.localStorage.clear(); window.name=data; </script>
這樣我們便可以實現在JSONP1的域名下面訪問到JSONP2中的本地存儲了,大家特別高興有沒有,反正這個需求當時做得時候也是挺困擾我的。
這里有一些注意事項要特別說明:
1、這個是在網上經過查找的代碼,也可以說是一個比較標准的使用代碼,大家在使用的時候可以參照業務需求進行該造。
2、在使用iframe這個標簽之后要進行銷毀,避免出現安全問題
3、window.name的使用必須建立在http協議的基礎之上的,換句話來說就是不能直接打開網頁一定要配置相應的本地域名(直接打開本地網頁采用的是file協議)
這個的實現思路跟cookie跨域相似,都是在兩個關聯域名中設置document.domain值,然后讓這兩個值相等,這樣就可以實現跨域操作,具體實現不給出,自行百度
cookie跨域這個沒有什么好說的,不清楚的同學請看我之前寫過的一篇文章:cookie學習指南
我們還是來實現一個上面的功能,在jsonp1域名下面獲取jsonp2中localStorage的test字段的值,嘗試着用postMessage來實現,具體的實現方式如下:
jsonp1.com下面的index.htm如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="test"></div> <textarea id="textarea"></textarea> <iframe style="width:0px;height:0px" id="f" src="http://www.jsonp2.com/demo.html"></iframe> <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.1/jquery.js"></script> <script> var test1=''; onmessage=function(e){ e=e||event; // console.log(e); // console.log(e.data); test1=e.data; if(test1=="123"){ alert("success!"); }else{ alert("error"); } $("#test").html("<span style='color:red'>"+test1+"</span>"); }; </script> </body> </html>
jsonp2.com中的demo.html內容:
<iframe id="f" src="http://www.jsonp1.com/index.html"></iframe> <script> var f=document.getElementById("f"); f.onload=function(){ window.localStorage.setItem("test","123"); var value=window.localStorage.getItem("test"); window.localStorage.clear(); f.contentWindow.postMessage(value,"http://www.jsonp1.com"); } </script>
這樣就實現了一個從Jsonp2中獲取本地存儲的功能,但是在實踐的過程中存在的一些問題需要引起我們的留意:
1、在兩個需要跨域的文件都需要引入一個iframe來加載對方的路徑
2、我們在使用的時候,是使用postMessage來發送信息給對方,然后我們是通過監控message事件來獲取信息的
三、總結
這個篇文章主要總結了一些關於前端跨域方面的工作實踐,與一些問題的探索,同時如果發現錯誤的話, 也希望各位能夠指正。