什么是跨域
1.跨域的定義
廣義的跨域是指一個域下對的文檔或者腳本試圖去請求另外一個域下的資源。
- a鏈接、重定向、表單提交
<frame>、<link>、<script>、<img>等標簽background:url()、@font-face()- ajax 跨域請求
- ……
狹義的跨域是指瀏覽器同源策略限制的一類請求場景。
####同源策略

前端跨域的主要解決方法
1.jsonp跨域
原理:動態創建<script>標簽,然后利用<script>的src不受同源策略約束來跨域獲取數據。
缺點:只支持get方式請求
- 原生js實現
1 var script = document.createElement('script'); 2 script.type = 'text/javascript'; 3 4 // 傳參一個回調函數名給后端,方便后端返回時執行這個在前端定義的回調函數 5 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=jsonPCallback'; 6 document.head.appendChild(script); 7 8 // 前端回調執行函數 9 function jsonPCallback(res) { 10 alert(JSON.stringify(res)); 11 } 12 13 //服務端返回如下(后端返回執行函數): 14 jsonPCallback({"status": true, "user": "admin"})
- jquery實現
1 $.ajax({ 2 url: 'http://www.domain2.com:8080/login', 3 type: 'get', 4 dataType: 'jsonp', // 請求方式為jsonp 5 jsonpCallback: "handleCallback", // 自定義回調函數名 6 data: {} 7 });
2.CORS(跨域資源共享)
CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低於IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
與jsonp相比:支持所有類型的HTTP請求。但JSONP支持老式瀏覽器。
1.簡單請求
(1) 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
同時滿足以上兩個條件的就是簡單請求
1 //簡單請求,瀏覽器自動增加Origin字段,Origin字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。 2 Content-Type: text/plain 3 Origin: http://www.domain.com 4 User-Agent: Mozilla/5.0 5 6 //如果Origin指定的源,不在許可范圍內,服務器會返回一個正常的HTTP回應。瀏覽器檢查這個響應的頭信息有沒有包含Access-Control-Allow-Origin字段,沒有的話,就會拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因為HTTP回應的狀態碼有可能是200。 7 //如果Origin指定的域名在許可范圍內,服務器返回的響應,會多出幾個頭信息字段。 8 9 Access-Control-Allow-Origin: http://www.domain.com 10 //值要么是請求時Origin字段的值,要么是一個*,表示接受任意域名的請求。 11 12 Access-Control-Allow-Credentials: true 13 //值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設為true,如果服務器不要瀏覽器發送Cookie,刪除該字段即可。 14 同時,請求中也要設置 15 var xhr = new XMLHttpRequest(); 16 xhr.withCredentials = true; 17 //Access-Control-Allow-Origin此時不能設為星號 18 19 Access-Control-Expose-Headers: FooBar 20 //用於拿到XMLHttpRequest對象非基本字段
2.復雜請求
1 //與簡單請求不同的是,復雜請求多了2個字段,進行服務器預檢: 2 Access-Control-Request-Method:該次請求的請求方式 3 Access-Control-Request-Headers:該次請求的自定義請求頭字段 4 5 //預檢成功,服務器返回的響應 6 //指定允許其他域名訪問 7 'Access-Control-Allow-Origin: 8 //是否允許后續請求攜帶認證信息(cookies),該值只能是true,否則不返回 9 'Access-Control-Allow-Credentials:true' 10 //預檢結果緩存時間 11 'Access-Control-Max-Age: 1800' 12 //允許的請求類型 13 'Access-Control-Allow-Methods:GET,POST,PUT,POST' 14 //允許的請求頭字段 15 'Access-Control-Allow-Headers:x-requested-with,content-type'
4.iframe 家族
1.window.name
window.name有以下特征:
- 每個窗口都有獨立的window.name與之對應;
- 在一個窗口被關閉前,窗口載入的所有頁面同時共享一個window.name,每個頁面對window.name都有讀寫的權限;
- window.name一直存在與當前窗口,即使是有新的頁面載入也不會改變window.name的值;
- window.name可以存儲最多2M的數據,數據格式按需自定義。
原理:在頁面中動態創建一個iframe頁面指向另一個域,將數據賦值給ifram的window.name屬性。(此時頁面不能直接讀取iframe的window.name),我們還需要將將iframe的src指向相同域的空白頁面。之后再將iframe刪除就可以了
1 <!--a.html--> 2 var proxy = function(url, callback) { 3 var state = 0; 4 var iframe = document.createElement('iframe'); 5 6 // 加載跨域頁面 7 iframe.src = url; 8 9 // onload事件會觸發2次,第1次加載跨域頁,並留存數據於window.name 10 iframe.onload = function() { 11 if (state === 1) { 12 // 第2次onload(同域proxy頁)成功后,讀取同域window.name中數據 13 callback(iframe.contentWindow.name); 14 destoryFrame(); 15 16 } else if (state === 0) { 17 // 第1次onload(跨域頁)成功后,切換到同域代理頁面 18 iframe.contentWindow.location = 'http://www.domain.com/aa.html'; 19 state = 1; 20 } 21 }; 22 23 document.body.appendChild(iframe); 24 25 // 獲取數據以后銷毀這個iframe,釋放內存;這也保證了安全(不被其他域frame js訪問) 26 function destoryFrame() { 27 iframe.contentWindow.document.write(''); 28 iframe.contentWindow.close(); 29 document.body.removeChild(iframe); 30 } 31 }; 32 // 請求跨域b頁面數據 33 proxy('http://www.domain1.com/b.html', function(data){ 34 alert(data); 35 }); 36 37 <!--b.html--> 38 <script> 39 window.name = 'This is domain1 data!'; 40 </script>
2.document.domain
主域相同,子域不同的跨域應用場景。
原理:兩個頁面都通過js強制設置document.domain為基礎主域,就實現了同域。
1 <!--a.html--> 2 <iframe id="iframe" src="http://child.domain.com/b.html"></iframe> 3 <script> 4 document.domain = 'domain.com'; 5 var user = 'admin'; 6 </script> 7 <!--b.html--> 8 <script> 9 document.domain = 'domain.com'; 10 // 獲取父窗口中變量 11 alert('get js data from parent ---> ' + window.parent.user); 12 </script>
3.location.hash
location.hash:指的是URL的#后面的部分,比如www.domain1.com/b.html#hello 的#hello,只改變hash是不會刷新頁面。
原理:通過中間頁面來實現。 三個頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通信。
1 <!--http://www.domain.com/a.html--> 2 <iframe id="iframe" src="http://www.domain1.com/b.html" style="display:none;"></iframe> 3 <script> 4 var iframe = document.getElementById('iframe'); 5 6 // 向b.html傳hash值 7 setTimeout(function() { 8 iframe.src = iframe.src + '#user=admin'; 9 }, 1000); 10 11 // 開放給同域c.html的回調方法 12 function onCallback(res) { 13 alert('data from c.html ---> ' + res); 14 } 15 </script> 16 17 <!--http://www.domain1.com/b.html--> 18 <iframe id="iframe" src="http://www.domain.com/c.html" style="display:none;"></iframe> 19 <script> 20 var iframe = document.getElementById('iframe'); 21 22 // 監聽a.html傳來的hash值,再傳給c.html 23 window.onhashchange = function () { 24 iframe.src = iframe.src + location.hash; 25 }; 26 </script> 27 28 <!--http://www.domain.com/c.html--> 29 <script> 30 // 監聽b.html傳來的hash值 31 window.onhashchange = function () { 32 // 再通過操作同域a.html的js回調,將結果傳回 33 window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); 34 }; 35 </script>
5.window.postMessage
otherWindow.postMessage(message, targetOrigin, [transfer]);
window.postMessage() 方法被調用時,會在所有頁面腳本執行完畢之后(e.g., 在該方法之后設置的事件、之前設置的timeout 事件,etc.)向目標窗口派發一個 MessageEvent 消息。 該MessageEvent消息有四個屬性需要注意: message 屬性表示該message 的類型; data 屬性為 window.postMessage 的第一個參數;origin 屬性表示調用window.postMessage() 方法時調用頁面的當前狀態; source 屬性記錄調用 window.postMessage() 方法的窗口信息。
優勢:頁面和其打開的新窗口的數據傳遞、 多窗口之間消息傳遞、嵌套的iframe消息傳遞的信息傳遞
1 <!--http://www.domain.com/a.html--> 2 <iframe id="iframe" src="http://www.domain1.com/b.html" style="display:none;"></iframe> 3 <script> 4 var iframe = document.getElementById('iframe'); 5 iframe.onload = function() { 6 var data = { 7 name: 'aym' 8 }; 9 // 向domain1傳送跨域數據 10 iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain1.com'); 11 }; 12 13 // 接受domain返回數據 14 window.addEventListener('message', function(e) { 15 alert('data from domain2 ---> ' + e.data); 16 }, false); 17 </script> 18 19 <!--http://www.domain1.com/b.html--> 20 <script> 21 // 接收domain的數據 22 window.addEventListener('message', function(e) { 23 alert('data from domain ---> ' + e.data); 24 25 var data = JSON.parse(e.data); 26 if (data) { 27 data.number = 16; 28 // 處理后再發回domain 29 window.parent.postMessage(JSON.stringify(data), 'http://www.domain.com'); 30 } 31 }, false); 32 </script>
注意:
如果您不希望從其他網站接收message,請不要為message事件添加任何事件偵聽器。
如果您確實希望從其他網站接收message,請始終使用origin和source屬性驗證發件人的身份。
當您使用postMessage將數據發送到其他窗口時,始終指定精確的目標origin,而不是*
6. nginx
7. Nodejs中間件代理
