如今cross-origin resource sharing(跨域資源共享,下簡稱CORS)已經十分普及,算上IE8的不標準兼容(XDomainRequest),各大瀏覽器基本都已支持,當年爲了先後端分離、iframe交互和第三方插件開發而頭疼跨域是時代已通過去,但當年爲了跨域無所不用其極的風騷操做卻依然值得學習。
本篇文章不是從實用的角度考量這些舊時代的跨域手段,而是更偏向理論的闡述,並引起對瀏覽器安全的思考,由於跨域實際上也是各種攻擊的核心。
本人我的能力有限,歡迎大牛一塊兒討論,批評指正。javascript
1995年,同源政策由Netscape公司引入瀏覽器。目前,全部瀏覽器都實行這個安全策略。
核心是確保不一樣源提供的文件(資源)之間是相互獨立的。換句話說,只有當不一樣的文件腳本是由相同的域、端口、HTTP協議提供時,纔沒有特殊的限制。特殊限制能夠細分爲兩個方面:html
一個表格看懂什麼是同源?前端
origin(URL) | result | reason |
---|---|---|
http://example.com |
success | 協議,域名和端口號80均相同 |
http://example.com:8080 |
fail | 端口不一樣 |
https://example.com |
fail | 協議不一樣 |
http://sub.example.com |
fail | 域名不一樣 |
至於爲何說這是個安全策略?
這個就要提到cookie-session機制,衆所周知HTTP是無狀態協議,而服務器如何知曉用戶的登陸狀態?傳統上是使用了cookie-session這一機制,也就是服務器爲每一個訪問者生成了一個session標識,而session標識會被服務器包含在應答頭中返回,瀏覽器解析到應答頭中的set-cookie就把這串session標識保存到本地cookie中,利用cookie每次請求同一個域都會帶上的特性,服務器器就能知曉當前的用戶登陸狀態。
因此若是讓瀏覽器向不一樣源發起請求,就會形成很大的危險。好比用戶登陸了銀行的網站A,也就是說A站已經在瀏覽器留下了cookie,這時候用戶又訪問了B站,若是能在B站頁面上發起A站的請求,就至關於B站能夠冒充用戶,在A站隨心所欲。
因而可知,"同源策略"是必需的,不然cookie能夠共享,互聯網就毫無安全可言了。java
同源策略提出的時代仍是傳統MVC架構(jsp,asp)盛行的年代,那時候的頁面靠服務器渲染完成了大部分填充,內容也比較簡單,開發者也不會維護獨立的API工程,因此其實跨域的需求是比較少的。
新時代先後端的分離和第三方JSSDK的興起,咱們纔開始發現這個策略雖然大大提升了瀏覽器的安全性,但有時很不方便,合理的用途也受到影響。好比:jquery
因而乎,在沒有標準規範的時代,如何解決這些問題的跨域方案就被紛紛提出,可謂百家爭鳴,其中不乏使人驚歎的騷操做,這樣的極客精神依然值得咱們敬佩和學習。ajax
JSON-P是各種跨域方案中流行度較高的一個,如今在某些要兼容舊瀏覽器的環境下還會被使用,著名的jQuery也封裝其方法。請勿見名知義,名字中的P是padding「帶填充」的意思,這個方法在通訊過程當中使用的並非普通的json,而是自帶填充功能的JavaScript腳本。
如何理解「自帶填充功能的JavaScript腳本」,看看下面的例子或許比較簡單,若是一個js文件裏這樣寫並被引入,則全局下就會有data對象,也就是說利用js腳本的引入和解析能夠用來傳遞數據,若是把js腳本換成函數運行命令豈不是能夠調用全局函數了。這就是JSON-P方法的核心思想,它填充的是全局函數的數據。json
var data = { a: 1, b: 2 }
【PS】
<script>
標籤不受同源策略限制,但只能發起get請求。
原理及流程後端
<script>
標籤,將標籤插入頁面瀏覽器便會發起get請求;// 定義回調函數 function getTheAnimal(data){ var myAnimal = data.animal; } // 新建標籤 var script = document.createElement("script"); script.type = "text/javascript"; // 經常使用的在url參數部分跟服務器約定號回調函數名 script.src = "http://demo.com/animal.json?callback=getTheAnimal"; document.getElementByTagName('head')[0].appendChild(script);
總結 api
優勢:跨域
缺點:
這個方法其實是利用瀏覽器容許iframe內的頁面只要是跟父頁面是同個一級域名下,就能被父頁面修改和調用的特色。也許你會疑問,上面講同源策略的表格中很明確二級域名不一樣也是算不一樣源,這豈不矛盾了?
這其實不矛盾,若是正常操做確實會被同源策略限制,但瀏覽器的document.domain
容許網站將主機部分更改成原始值的後綴。這意味着,寄放在sub.example.com的頁面能夠將它的源設置爲example.com,可是並不能將其設置爲alt.example.com或google.com。
【PS】這裏有一個細節,父子頁面均要設置document.domain
才能被互相訪問,單一一個是沒法跨域的。document.domain
的特色:只能設置一次;只能更改域名部分,不能修改端口號和協議;重置源的端口爲協議默認端口。
原理及流程
document.domain = 'demo.com'
,並能夠包含發起ajax的工具;document.domain = 'demo.com'
;// 最簡單的代理文件proxy.html <!DOCTYPE html> <html> <script> document.domain = 'demo.com'; </script> <script src="jquery.min.js"></script> </html>
// 新建iframe var iframe = document.createElement('iframe'); // 連接到代理頁 iframe.src = 'http://api.demo.com/proxy.html'; // 代理頁就緒時觸發 iframe.onload = function(){ // 因爲代理頁已經和父頁設置了相同的源,父的腳本能夠調用代理頁的ajax工具; // 因爲是在子頁面發起,其請求地址就跟子頁面同源了。 iframe.contentWindow.jQuery.ajax({ method: 'POST', url: 'http://api.demo.com/products', data: { product: id, }, success: function(){ document.body.removeChild(iframe); /*...*/ } }) } document.getElementsByTagName('head')[0].appendChild(iframe);
總結
優勢:
缺點:
form表單的target屬性能夠指定一個iframe,使主頁面不跳轉,而iframe內跳轉,因此這個方法的核心就是利用表單提交,並在iframe中獲取數據。
要訪問iframe內外頁面互訪也是必須設置同源,這點與子域代理是類似的;而iframe內回調父頁面,又與JSON-P類似,能夠說是兩個思路的合體版。
form表單提交後返回的是頁面,因此與JSON-P不一樣的是,返回的是包含了自帶填充功能的JavaScript腳本的頁面,提及來有點繞,簡單來講就是把JSON-P返回的腳本放到一個html頁面裏自運行。
相比子域代理的方法,它不須要代理頁。
【PS】form表單提交的特色就是會致使整個頁面跳轉,返回數據是在新的頁面上,這樣天然不會產生跨域的問題。
原理及流程
document.domain = 'demo.com'
;// 新建並隱藏iframe var frame = document.createElement('iframe'); iframe.name = 'post-review'; frame.style.display = 'none'; // 新建表單 var form = document.createElement('form'); form.action = 'http://api.demo.com/products'; form.method = 'POST'; form.target = 'post-review'; // 添加數據 var score = document.createElement('input'); score.name = 'score'; score.value = '5'; // 添加數據 var message = document.createElement('input'); message.name = 'message'; message.value = 'hello world'; // 把數據加到表單 form.appendChild(score); form.appendChild(message); // 渲染iframe和表單 document.body.appendChild(frame); document.body.appendChild(form); // 提交表單發起請求 form.submit(); // 完成清理元素 document.body.removeChild(form); document.body.removeChild(frame);
// 最簡單返回html <!DOCTYPE html> <html> <script> document.domain = 'demo.com'; window.parent.jsonpCallback('{"status":"success"}'); </script> </html>
總結
因爲這個方法是JSON-P與子域名代理的結合版,能夠說即擁有二者的優勢,也保留了二者一些缺點。
優勢:
缺點:
這方法利用了window.name
的特性:一旦被賦值後,當窗口被重定向到一個新的URL時不會改變它的值。這一行爲使得不一樣域的特定文檔能夠讀取該屬性值,所以能夠繞過同源策略並使跨域消息通訊成爲可能。
【PS】例子裏演示的是發起get請求,只要把請求地址直接寫到src裏就好了。若是想要發起其餘類型的請求,能夠類比採用模擬的form的方式進行改造。
原理及流程
// 新建iframe var iframe = document.createElement('iframe'); var body = document.getElementByTagName('body'); // 隱藏iframe並連接地址 iframe.style.display = 'none'; iframe.src = 'http://api.demo.com/server.html?id=1'; // 由於須要兩次跳轉,這裏有個完成標記 var done = fasle; // 這裏會觸發至少兩次,一次因爲非同源是取不到值的。 iframe.onload = iframe.onreadystatechange = function(){ if(! this.readyState && (iframe.readyState !== 'complete' || done)){ return; } console.log('Listening'); var name = iframe.contentWindow.name; if(name){ console.log(iframe.contentWindow.name); done = true; } }; body.appendChild(iframe);
// 最簡單返回html <!DOCTYPE html> <html> <script> function init(){ window.name = 'hello'; window.location = 'http://demo.com/empty.html' } </script> <body onload="init();"></body> </html>
總結
優勢:
缺點:
這個方法利用了location的特性:不一樣域的頁面,能夠寫不可讀。而只改變哈希部分(井號後面)不會致使頁面跳轉。也就是可讓父、子頁面互相寫對方的location的哈希部分,進行通信。
原理及流程
【PS】父頁面會循環檢查哈希是否改變來讀取值,由於這種降級方案的使用環境通常是不會有hashchange事件的。演示裏是最簡單的get方法,若是想要發起其餘類型的請求,能夠類比採用模擬的form的方式進行改造,但記住不要丟失父頁面的url。
// 獲取當前url var url = window.location.href; // 新建iframe var iframe = document.createElement('iframe'); // 隱藏iframe並設置連接,把當前url帶上 iframe.style.display = 'none'; iframe.src = 'http://api.demo.com/server.html?id=1&url=' + encodeURIComponent(url); var body = document.getElementByTagName('body')[0]; body.appendChild(iframe); // 循環監聽處理 var listener = function(){ // 讀取 var hash = location.hash; // 還原 if(hash && hash !== '#'){ console.log(hash.replace('#', '')); window.loacation.href = url + '#'; } // 繼續監聽 setTimeout(listener, 100); }; listener();
// 最簡單返回html <!DOCTYPE html> <html> <script> function init(){ // 剪裁出父頁面的url var parentUrl = ''; var url = window.location.href; var str = url.split('?')[1].replace('?', ''); strs = str.split("&"); for(var i = 0; i < strs.length; i ++) { if(strs.split("=")[0] === 'url'){ parentUrl = strs.split("=")[1]; } } // 設置到父頁面上 window.parent.location = decodeURIComponent(parentUrl) + '#helloworld'; } </script> <body onload="init();"></body> </html>
總結
優勢:
缺點:
W3C的標準化跨域方案,讓現代瀏覽器跨域已經不是什麼複雜的事。這部分網上資料已經不少,這裏就只是簡單介紹。
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。
它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
H5的window.postMessage爲瀏覽器帶來了一個安全的。基於事件的消息api。
只要是window對象,基本均可以使用這個方法,也就是說window.name、window.hash這類風騷的操做都已成爲降級方案。
上述的各種非標準的騷操做,都算是對同源策略的破解辦法,在方便開發者完成跨域目的的同時,各種惡意的攻擊者也天然會利用這些方案爲非做歹。 其中子域名代理的風險最低,由於須要服務器設置特定的子域名,也就是已是兩個源的協商結果,通常黑客是難以模擬的。 風險最高的要算JSON-P的方案,由於這是任何客戶端均可隨意使用的辦法,CSRF攻擊的核心也是利用了特定標籤的跨域性發起請求,因此JSON-P最好用在無用戶狀態的低安全性API上。