前端開發中我們經常會遇到跨域請求的情況,處理跨域請求方式很多大概分為八種。
瀏覽器的同源策略
提到跨域不能不先說一下”同源策略”。
何為同源?只有當協議、端口、和域名都相同的頁面,則兩個頁面具有相同的源。只要網站的 協議名protocol、 主機host、 端口號port 這三個中的任意一個不同,網站間的數據請求與傳輸便構成了跨域調用,會受到同源策略的限制。
同源策略限制從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用於隔離潛在惡意文件的關鍵的安全機制。瀏覽器的同源策略,出於防范跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對不同域的服務進行跨站調用(通常指使用XMLHttpRequest請求)。
特別注意兩點:
1、如果是協議和端口造成的跨域問題“前台”是無能為力的;
2、在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。
URL的首部:指window.location.protocol +window.location.host,也可以理解為“Domains(域名), protocols(協議) and ports(端口) must match”。
跨域請求方式
解決跨域問題,最簡單的莫過於通過nginx反向代理進行實現,但是其需要在運維層面修改,且有可能請求的資源並不再我們控制范圍內(第三方),所以該方式不能作為通用的解決方案,下面闡述了經常用到幾種跨域方式:
方式一:圖片ping或script標簽跨域
圖片ping常用於跟蹤用戶點擊頁面或動態廣告曝光次數。
script標簽可以得到從其他來源數據,這也是JSONP依賴的根據。
缺點:只能發送Get請求 ,無法訪問服務器的響應文本(單向請求)
方式二:JSONP跨域
JSONP(JSON with Padding)是數據格式JSON的一種“使用模式”,可以讓網頁從別的網域要數據。根據 XmlHttpRequest 對象受到同源策略的影響,而利用 <script>元素的這個開放策略,網頁可以得到從其他來源動態產生的JSON數據,而這種使用模式就是所謂的 JSONP。用JSONP抓到的數據並不是JSON,而是任意的JavaScript,用 JavaScript解釋器運行而不是用JSON解析器解析。所有,通過Chrome查看所有JSONP發送的Get請求都是js類型,而非XHR。
缺點:
- 只能使用Get請求
- 不能注冊success、error等事件監聽函數,不能很容易的確定JSONP請求是否失敗
- JSONP是從其他域中加載代碼執行,容易受到跨站請求偽造的攻擊,其安全性無法確保
方式三:使用跨域資源共享(CORS)來跨域
Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規范,提供了 Web 服務從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,確保安全的跨域數據傳輸。現代瀏覽器使用CORS在API容器如XMLHttpRequest來減少HTTP請求的風險來源。與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。服務器一般需要增加如下響應頭的一種或幾種:
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400
跨域請求默認不會攜帶Cookie信息,如果需要攜帶,請配置下述參數:
"Access-Control-Allow-Credentials": true // Ajax設置 "withCredentials": true
方式四:window.name+iframe
window.name通過在iframe(一般動態創建i)中加載跨域HTML文件來起作用。然后,HTML文件將傳遞給請求者的字符串內容賦值給window.name。然后,請求者可以檢索window.name值作為響應。
iframe標簽的跨域能力;
window.name屬性值在文檔刷新后依舊存在的能力(且最大允許2M左右)。
每個iframe都有包裹它的window,而這個window是top window的子窗口。contentWindow屬性返回<iframe>元素的Window對象。你可以使用這個Window對象來訪問iframe的文檔及其內部DOM。
<!-- 下述用端口 10000表示:domainA 10001表示:domainB --> <!-- localhost:10000 --> <script> var iframe = document.createElement('iframe'); iframe.style.display = 'none'; // 隱藏 var state = 0; // 防止頁面無限刷新 iframe.onload = function() { if(state === 1) { console.log(JSON.parse(iframe.contentWindow.name)); // 清除創建的iframe iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if(state === 0) { state = 1; // 加載完成,指向當前域,防止錯誤(proxy.html為空白頁面) // Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame. iframe.contentWindow.location = 'http://localhost:10000/proxy.html'; } }; iframe.src = 'http://localhost:10001'; document.body.appendChild(iframe); </script> <!-- localhost:10001 --> <!DOCTYPE html> ... <script> window.name = JSON.stringify({a: 1, b: 2}); </script> </html>
直接嵌入其他域(localhots:10001)下的URL會報錯,所以需要加載完成替換為當前域的URL(localhots:10000),proxy.html為空白頁面,只為解決該問題;
重新設置src(http://localhost:10000/proxy.html)后導致頁面不斷刷新,所以通過state來控制;
全部獲取完結果后,清除該iframe。
方式五:window.postMessage()(不常用)
window.postMessage(message,targetOrigin) 方法是html5新引進的特性,可以使用它來向其它的window對象發送消息,無論這個window對象是屬於同源或不同源(可實現跨域),目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。
message:為要發送的消息,類型只能為字符串;
targetOrigin:用來限定接收消息的那個window對象所在的域,如果不想限定域,可以使用通配符 “*”。
1)創建www.test.com/a.html頁面代碼:
<script> function onLoad(){ var iframe = document.getElementById("iframe"); var win = iframe.contentWindow; win.postMessage('哈哈,我是來自頁面a.html的信息喲!','*');//向不同域的www.script.com/b.html發送消息 } </script> <iframe id="iframe" src="www.script.com/b.html" onload="onLoad()"></iframe>
2)創建www.script.com/b.html頁面代碼:
<script> window.onmessage = function(e){//注冊message時間來接收消息 e = e || event; //獲取時間對象 alert(e.data); //通過data屬性來得到傳送的消息 } </script>
優點:使用postMessage來跨域傳送數據還是比較直觀和方便的;
缺點: IE6、IE7不支持,所以用不用還得根據實際需要來決定。
方式六:通過document.domain+iframe來跨子域(必須主域相同)
前提條件:這兩個域名必須屬於同一個基礎域名!而且所用的協議,端口都要一致,否則無法利用document.domain進行跨域,所以只能跨子域
在根域范圍內,允許把domain屬性的值設置為它的上一級域。例如,在”aaa.xxx.com”域內,可以把domain設置為 “xxx.com” 但不能設置為 “xxx.org” 或者”com”。
現在存在兩個域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的頁面,由於其document.name不一致,無法在aaa下操作bbb的js。可以在aaa和bbb下通過js將document.name = 'xxx.com';設置一致,來達到互相訪問的作用。
方式七:WebSocket
WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很棒的實現。相關文章,請查看:WebSocket、WebSocket-SockJS
需要注意:WebSocket對象不支持DOM 2級事件偵聽器,必須使用DOM 0級語法分別定義各個事件。
方式八:代理
同源策略是針對瀏覽器端進行的限制,可以通過服務器端來解決該問題
DomainA客戶端(瀏覽器) ==> DomainA服務器 ==> DomainB服務器 ==> DomainA客戶端(瀏覽器)
實現HTTP、HTTPS代理請參照: 創建HTTP與HTTPS服務器與客戶端