平常開發中最常與網絡打交道,那關於瀏覽器的同源策略和跨域相關的知識是該整理一下了。
首先須要明確的是,同源策略是瀏覽器的安全策略,因爲存在這個策略,咱們才須要對各類跨域需求進行處理。html
同源策略的主要目的是爲了保護用戶的信息安全。前端
同源的含義其實比較好理解,實際上就是三點ajax
url | 說明 | 是否同源 |
---|---|---|
http://www.test.com https://www.test.com | 同一域名,不一樣協議 | 不一樣源 |
http://www.test.com https://www.test1.com | 不一樣域名 | 不一樣源 |
http://www.test.com:8080 https://www.test.com:8081 | 同一域名,不一樣端口 | 不一樣源 |
https://www.test.com http://192.168.1.1 | 域名和域名對應的 ip | 不一樣源 |
一、Cookie、localStorage 和 IndexDB 沒法讀取
二、DOM 沒法得到
三、AJAX 請求不能發送算法
cookie 的讀取與其餘屬性有些不一樣。由於 cookie 的可見性是由 domain 屬性和 path 屬性決定的,因此他其實不受協議和端口的限制。只要是同一個 domain 和可見 path 下的 cookie,都能讀取到。segmentfault
因爲 cookie 的讀取自己具備侷限性,因此跨域後的規避能夠這樣操做跨域
Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'test' is not a suffix of 'localhost'.
localStorage、IndexedDB 和 DOM 是實實在在受同源策略限制的,協議、域名和端口任意一項不相同就沒法讀取。
其實對於他們的讀取能夠簡化成不一樣源的兩個頁面如何通訊。
自己 localStorage 和 IndexedDB 的出現就是爲了讓咱們可以更好地存儲數據,而獲取 DOM 大多狀況也是爲了獲取 DOM 中的各類數據。泛化到業務場景中,也就是傳遞數據的事兒了。瀏覽器
目前能夠經過如下幾種方式來進行通訊。安全
window.name
使用 window.name 的原理是同源 iframe 能夠讀取 contentWindow。具體操做以下
一、a 頁面先載入一個不一樣源的 iframe 頁面 b
二、在 b 頁面 中修改 window.name 爲須要的傳遞的數據
三、a 頁面中修改這個 iframe 的 src 爲同源的頁面 c
四、a 頁面獲取以前設置的 window.name
按照這樣的方式進行操做就能獲取到子頁面中的數據。若是子頁面想要獲取父頁面中的數據,能夠將 一、3 步驟換一下,2 步驟改爲父頁面直接修改 iframe.contentWindow.name 爲須要傳遞的數據便可。
由於是經過 name 屬性來傳遞參數,因此可傳遞的數據量很大,基本就是字符串長度的最大值。服務器
//記錄 iframe onload 事件的加載次數 var state = 0; var iframe = document.createElement("iframe"); // 加載跨域頁面 iframe.src = "http://sub.test.com/test/test.html"; // onload 事件會觸發2次,第1次加載跨域頁,在跨域頁中將所須要的數據賦給 window.name iframe.onload = function () { if (state === 1) { // 第2次 onload 成功後,讀取同域 window.name 中數據 var data = iframe.contentWindow.name; } else if (state === 0) { // 第1次onload(跨域頁)成功後,切換到同域代理頁面 iframe.contentWindow.location = "./testB.html"; state = 1; } }; document.body.appendChild(iframe);
fragment identifier 片斷標識符
fragment identifier 其實就是 url # 後面的部分。常寫 SPA 應用的小夥伴應該會對他很熟悉,由於 hash 模式的 router 就是基於他實現的。修改嵌入的 iframe 的 src 爲 url#須要傳遞的數據,iframe 頁面就能經過監聽 hashchange 事件得到傳遞的數據。若是是子頁面向父頁面傳遞數據要多加一步,得先讓父頁面把本身當前的 url 傳遞給子頁面,而後子頁面去修改父頁面的 href。
使用這個方法傳遞數據的限制暫時還未測試,猜想應該會和瀏覽器限制 url 的長度有關。cookie
//父->子 a頁面 var iframe = document.createElement("iframe"); // 加載跨域頁面 iframe.src = url; setTimeout(function () { iframe.src = url + "#a=111"; }, 1000); document.body.appendChild(iframe); //父->子 b頁面 window.onhashchange = function () { alert(window.location.hash) }
//子->父 a頁面 var iframe = document.createElement("iframe"); // 加載跨域頁面 iframe.src = url; setTimeout(function () { var selfurl = document.location.href; iframe.src = url + "#" + selfurl; }, 1000); document.body.appendChild(iframe); window.onhashchange = function () { alert("from son" + window.location.hash); }; //子->父 b頁面 window.onhashchange = function () { let target = window.location.hash.slice(1); window.parent.parent.location.href = target + "#111aaaa" }
postMessage
嚴格意義上來講上面兩種方法都是對跨域頁面獲取數據的破解,postMessage 纔是正統的非同源頁面之間傳遞數據的方法。
window.postMessage() 方法提供了一種受控機制來規避此限制,只要正確的使用,這種方法就很安全。
postMessage 是新增的 API,使用前記得查看一下兼容性。
使用上須要注意的就是調用 postMessage 和監聽 message 事件的的主體是同一個。也就是說子頁面向父頁面發送消息時,須要獲取 window.parent 去調用 postMessage。父頁面向子頁面發送消息時,能夠直接獲取 iframe,而後調用 postMessage。
postMessage((message, targetOrigin, [transfer]);
message
將要發送到其餘 window 的數據。它將會被結構化克隆算法序列化。這意味着你能夠不受什麼限制的將數據對象安全的傳送給目標窗口而無需本身序列化。
targetOrigin
經過窗口的 origin 屬性來指定哪些窗口能接收到消息事件,其值能夠是字符串""(表示無限制)或者一個 URI。
在發送消息的時候,若是目標窗口的協議、主機地址或端口這三者的任意一項不匹配 targetOrigin 提供的值,那麼消息就不會被髮送;只有三者徹底匹配,消息纔會被髮送。
這個機制用來控制消息能夠發送到哪些窗口;例如,當用 postMessage 傳送密碼時,這個參數就顯得尤其重要,必須保證它的值與這條包含密碼的信息的預期接受者的 origin 屬性徹底一致,來防止密碼被惡意的第三方截獲。
若是你明確的知道消息應該發送到哪一個窗口,那麼請始終提供一個有確切值的 targetOrigin,而不是。不提供確切的目標將致使數據泄露到任何對數據感興趣的惡意站點。
transfer 可選
是一串和message 同時傳遞的 Transferable 對象. 這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權
下面這個示例是子頁面向父頁面發送消息。
// a 頁面 window.addEventListener("message", function (event) { console.log(event); }, false); // b 頁面 var b = "bbb"; window.parent.postMessage({ b }, "*")
另外特別須要注意的是關於 DOM 的獲取,若是隻是兩個不一樣子域的頁面,將 document.domain 設置爲同一主域就能夠讀取相應數據。
//a 頁面 main.test.com var a = "aaa"; // domain 設置爲主域 document.domain = "test.com"; var iframe = document.createElement("iframe"); // 加載跨域頁面 iframe.src = "sub.test.com"; document.body.appendChild(iframe); iframe.onload = function(){ // 能夠獲取到變量 var data = iframe.contentWindow.b // 能夠獲取到 DOM var dom = iframe.contentWindow.document.body; } //b 頁面 sub.test.com var b = "bbb"; // domain 設置爲主域 document.domain = "test.com";
AJAX 請求跨域平常使用比較多,經常使用的方法有如下幾種
JSONP
這個方法是向服務器請求的時候,在 url 後面寫上 callback 方法的名字,請求返回其實是返回了一個 調用 callback 方法的 js 文件。須要返回的參數也就在調用的時候傳進去了。
因此侷限也很明確,只支持 get 方法。
使用代理服務器
全部的請求先發送給這個代理服務器,由這個代理服務器去請求實際的接口,再把須要的數據返回。
(這個方式就能夠真切地體會到,同源策略只是瀏覽器的一種安全策略)
使用 CORS Cross-Origin Resource Sharing 跨域資源共享
目前基本上全部的瀏覽器都支持 CORS,因此只須要服務器端進行處理。對於前端來講,請求和同源的 AJAX 請求是一致的。
前端發送請求的時候瀏覽器會自動帶上 origin 字段,服務器端去判斷這個 origin 是不是可接受的地址,在相應頭中設置 Access-Control-Allow-Origin 字段的值。
這樣前端就能正常獲取到數據啦。
這裏只對 CORS 作了一個簡單介紹,詳細的下次細說吧。
總結:
瀏覽器同源政策及其規避方法
瀏覽器的同源策略
window.postMessage
跨源資源共享(CORS)
前端常見跨域解決方案(全)