《前端跨域解決方案匯總-前端開發(fā)博客》由會員分享,可在線閱讀,更多相關(guān)《前端跨域解決方案匯總-前端開發(fā)博客(9頁珍藏版)》請?jiān)谘b配圖網(wǎng)上搜索。
1、前端跨域解決方案匯總-前端開發(fā)博客
2017-06-20 編者注:關(guān)于跨域的文章,之前分享過很多,來看看這篇前端跨域解決方案,由簡及深介紹各種存在的跨域請求解決方案,包括 document.domain, location.hash, window.name, window.postMessage, JSONP, WebSocket, CORS。原文:hijiangtao.github.io同源策略限制從一個源加載的文檔或腳本如何與來自另一個源的資源進(jìn)行交互。這是一個用于隔離潛在惡意文件的關(guān)鍵的安全機(jī)制。但是有時候跨域請求資源是合理的需求,本文嘗試從多篇文章中匯總至今存在的所有跨域請求解
2、決方案??缬蛘埱笫紫刃枰私獾氖峭春涂缭吹母拍睢τ谙嗤?,其定義為:如果協(xié)議、端口(如果指定了一個)和主機(jī)對于兩個頁面是相同的,則兩個頁面具有相同的源。只要三者之一任意一點(diǎn)有不同,那么就為不同源。當(dāng)一個資源從與該資源本身所在的服務(wù)器的域或端口不同的域或不同的端口請求一個資源時,資源會發(fā)起一個跨域 HTTP 請求。而有關(guān)跨域請求受到限制的原因可以參考如下 MDN 文檔片段:跨域不一定是瀏覽器限制了發(fā)起跨站請求,而也可能是跨站請求可以正常發(fā)起,但是返回結(jié)果被瀏覽器攔截了。最好的例子是 CSRF 跨站攻擊原理,請求是發(fā)送到了后端服務(wù)器無論是否跨域!注意:有些瀏覽器不允許從 HTTPS 的域跨域訪
3、問 HTTP,比如 Chrome 和 Firefox,這些瀏覽器在請求還未發(fā)出的時候就會攔截請求,這是一個特例。解決方法匯總以下我們由簡及深介紹各種存在的跨域請求解決方案,包括 document.domain, location.hash, window.name, window.postMessage, JSONP, WebSocket, CORS。document.domaindocument.domain 的作用是用來獲取/設(shè)置當(dāng)前文檔的原始域部分,例如:// 對于文檔 www.example.xxx/good.htmldocument.domain="www.example.xxx"/
4、/ 對于URI http://developer.mozilla.org/en/docs/DOM document.domain="developer.mozilla.org"如果當(dāng)前文檔的域無法識別,那么 domain 屬性會返回 null。在根域范圍內(nèi),Mozilla允許你把domain屬性的值設(shè)置為它的上一級域。例如,在 developer.mozilla.org 域內(nèi),可以把domain設(shè)置為 "mozilla.org" 但不能設(shè)置為 "" 或者"org"。因此,若兩個源所用協(xié)議、端口一致,主域相同而二級域名不同的話,可以借鑒該方法解決跨域請求。比如若我們在 http://a.gith
5、ub.io 頁面執(zhí)行以下語句:document.domain = "github.io"那么之后頁面對 github.io 發(fā)起請求時頁面則會成功通過對 github.io 的同源檢測。比較直接的一個操作是,當(dāng)我們在a.github.io 頁面中利用 iframe 去加載 github.io 時,通過如上的賦值后,我們可以在 a.github.io 頁面中去操作 iframe 里的內(nèi)容。我們同時考慮另一種情況:存在兩個子域名 a.github.io 以及 b.github.io, 其中前者域名下網(wǎng)頁 a.html 通過 iframe 引入了后者域名下的 b.html,此時在 a.html 中是
6、無法直接操作 b.html 的內(nèi)容的。同樣利用 document.domain,我們在兩個頁面中均加入document.domain=github.io這樣在以上的 a.html 中就可以操作通過 iframe 引入的 b.html 了。document.domain 的優(yōu)點(diǎn)在于解決了主語相同的跨域請求,但是其缺點(diǎn)也是很明顯的:比如一個站點(diǎn)受到攻擊后,另一個站點(diǎn)會因此引起安全漏洞;若一個頁面中引入多個 iframe,想要操作所有的 iframe 則需要設(shè)置相同的 domain。location.hashlocation.hash 是一個可讀可寫的字符串,該字符串是 URL 的錨部分(從 # 號
7、開始的部分)。例如:// 對于頁面 :1234/test.htm#part2location.hash = "#part2"同時,由于我們知道改變 hash 并不會導(dǎo)致頁面刷新,所以可以利用 hash 在不同源間傳遞數(shù)據(jù)。假設(shè) github.io 域名下 a.html 和 shaonian.eu 域名下 b.html 存在跨域請求,那么利用 location.hash 的一個解決方案如下:a.html 頁面中創(chuàng)建一個隱藏的 iframe, src 指向 b.html,其中 src 中可以通過 hash 傳入?yún)?shù)給 b.htmlb.html 頁面在處理完傳入的 hash 后通過修改 a.html
8、 的 hash 值達(dá)到將數(shù)據(jù)傳送給 a.html 的目的a.html 頁面添加一個定時器,每隔一定時間判斷自身的 location.hash 是否變化,以此響應(yīng)處理以上步驟中需要注意第二點(diǎn):如何在 iframe 頁面中修改 父親頁面的 hash 值。由于在 IE 和 Chrome 下,兩個不同域的頁面是不允許 parent.location.hash 這樣賦值的,所以對于這種情況,我們需要在父親頁面域名下添加另一個頁面來實(shí)現(xiàn)跨域請求,具體如下:假設(shè) a.html 中 iframe 引入了 b.html, 數(shù)據(jù)需要在這兩個頁面之間傳遞,且 c.html 是一個與 a.html 同源的頁面a.ht
9、ml 通過 iframe 將數(shù)據(jù)通過 hash 傳給 b.htmlb.html 通過 iframe 將數(shù)據(jù)通過 hash 傳給 c.htmlc.html 通過 parent.parent.location.hash 設(shè)置 a.html 的 hash 達(dá)到傳遞數(shù)據(jù)的目的location.bash 方法的優(yōu)點(diǎn)在于可以解決域名完全不同的跨域請求,并且可以實(shí)現(xiàn)雙向通訊;而缺點(diǎn)則包括以下幾點(diǎn):利用這種方法傳遞的數(shù)據(jù)量受到 url 大小的限制,傳遞數(shù)據(jù)類型有限由于數(shù)據(jù)直接暴露在 url 中則存在安全問題若瀏覽器不支持 onhashchange 事件,則需要通過輪訓(xùn)來獲知 url 的變化有些瀏覽器會在 ha
10、sh 變化時產(chǎn)生歷史記錄,因此可能影響用戶體驗(yàn)window.name該屬性用于獲取/設(shè)置窗口的名稱。其特征在于:一個窗口的生命周期內(nèi),窗口載入的所有頁面共享該值,且都具有對該屬性的讀寫權(quán)限。這意味著如果不修改該值,那么在不同頁面加載之后該值也不會變,且其支持長達(dá) 2MB 的存儲量。利用該特性我們可以將跨域請求用如下步驟解決:在 a.github.io/a.html 中創(chuàng)建 iframe 指向 b.github.io/b.html (頁面會將自身的 window.name 附在 iframe 上)給 a.github.io/a.html 添加監(jiān)聽 iframe 的 onload 事件,在該事件中
11、將 iframe 的 src 設(shè)置為本地域的代理文件(代理文件和a.html處于同一域下,可以相互通信),同時可以傳出 iframe 的 name 值獲取數(shù)據(jù)后銷毀 iframe,釋放內(nèi)存,同時也保證了安全window.name 的優(yōu)勢在于巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。window.postMessageHTML5 為了解決這個問題,引入了一個全新的 API:跨文檔通信 API(Cross-document messaging)。這個 API 為 window 對象新增了一個 window.postMessage 方法,允許跨窗口通信,不論這兩個窗口是否同源。API
12、的詳細(xì)使用方法請見 MDN。JSONPJSONP, 全稱 JSON with Padding,是使用 AJAX 實(shí)現(xiàn)的請求不同源的跨域。其基本原理:網(wǎng)頁通過添加一個 // test.js// 調(diào)用callback函數(shù),并以json數(shù)據(jù)形式作為闡述傳遞,完成回調(diào)callback({message:"success"}); 為了保證 script 的靈活,我們可以通過 JavaScript 動態(tài)創(chuàng)建 script 標(biāo)簽,并通過 HTTP 參數(shù)向服務(wù)器傳入回調(diào)函數(shù)名,案例如下所示:jQuery 有相應(yīng)的 JSONP 的實(shí)現(xiàn)方法,見 API。JSONP的優(yōu)點(diǎn)在于簡單適用,老式瀏覽器全部支持,服務(wù)器改造
13、小。不需要XMLHttpRequest或ActiveX的支持;但缺點(diǎn)是只支持 GET 請求。WebSocketWebSocket 協(xié)議不實(shí)行同源政策,只要服務(wù)器支持,就可以通過它進(jìn)行跨源通信。CORSCORS是一個W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制??缬蛸Y源共享( CORS )機(jī)制允許 Web 應(yīng)用服務(wù)器進(jìn)行跨域訪問控制,從而使跨域數(shù)據(jù)傳輸?shù)靡园踩M(jìn)行。其需要服務(wù)端和客戶端同時支持??缬蛸Y源共享標(biāo)準(zhǔn)( cross-origin sh
14、aring standard )允許在下列場景中使用跨域 HTTP 請求:由 XMLHttpRequest 或 Fetch 發(fā)起的跨域 HTTP 請求Web 字體 (CSS 中通過 @font-face 使用跨域字體資源), 因此,網(wǎng)站就可以發(fā)布 TrueType 字體資源,并只允許已授權(quán)網(wǎng)站進(jìn)行跨站調(diào)用WebGL 貼圖使用 drawImage 將 Images/video 畫面繪制到 canvas樣式表(使用 CSSOM)Scripts (未處理的異常)CORS 存在以下三種主要場景,分別是簡單請求,預(yù)檢請求和附帶身份憑證的請求。簡單請求:若只使用 GET, HEAD 或者 POST 請求,
15、且除 CORS 安全的首部字段集合外,無人為設(shè)置該集合之外的其他首部字段,同時 Content-Type 值屬于下列之一,那么該請求則可以被視為簡單請求:application/x-www-form-urlencodedmultipart/form-datatext/plain此情況下,若服務(wù)端返回的 Access-Control-Allow-Origin: * ,則表明該資源可以被任意外域訪問。若要指定僅允許來自某些域的訪問,需要將 * 設(shè)定為該域,例如:Access-Control-Allow-Origin: http://foo.example預(yù)檢請求:與前述簡單請求不同,該要求必須首先
16、使用 OPTIONS 方法發(fā)起一個預(yù)檢請求到服務(wù)器,以獲知服務(wù)器是否允許該實(shí)際請求。當(dāng)請求滿足以下三個條件任意之一時, 即應(yīng)首先發(fā)送預(yù)檢請求:使用了 PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 中任一的 HTTP 方法人為設(shè)置了對 CORS 安全的首部字段集合之外的其他首部字段Content-Type 的值不屬于下列之一application/x-www-form-urlencodedmultipart/form-datatext/plain預(yù)檢請求完成之后(通過 OPTIONS 方法實(shí)現(xiàn)),才發(fā)送實(shí)際請求。一個示范 HTTP 請求如下所示:var
17、invocation = new XMLHttpRequest();var url = http://bar.other/resources/post-here/;var body = Arun; function callOtherDomain(){ if(invocation) { invocation.open(POST, url, true); invocation.setRequestHeader(X-PINGOTHER, pingpong); invocation.setRequestHeader(Content-Type, applic
18、ation/xml); invocation.onreadystatechange = handler; invocation.send(body); }}附帶身份憑證的請求:這種方式的特點(diǎn)在于能夠在跨域請求時向服務(wù)器發(fā)送憑證請求,例如 Cookies (withCredentials 標(biāo)志設(shè)置為 true)。一般而言,對于跨域 XMLHttpRequest 或 Fetch 請求,瀏覽器不會發(fā)送身份憑證信息。如果要發(fā)送憑證信息,需要設(shè)置 XMLHttpRequest 的某個特殊標(biāo)志位。但是需要注意的是,如果服務(wù)器端的響應(yīng)中未攜帶 Access-Control-All
19、ow-Credentials: true,瀏覽器將不會把響應(yīng)內(nèi)容返回給請求的發(fā)送者。附帶身份憑證的請求與通配符對于附帶身份憑證的請求,服務(wù)器不得設(shè)置 Access-Control-Allow-Origin 的值為“*”。這是因?yàn)檎埱蟮氖撞恐袛y帶了 Cookie 信息,如果 Access-Control-Allow-Origin 的值為“*”,請求將會失敗。而將 Access-Control-Allow-Origin 的值設(shè)置為 http://foo.example,則請求將成功執(zhí)行。另外,響應(yīng)首部中也攜帶了 Set-Cookie 字段,嘗試對 Cookie 進(jìn)行修改。如果操作失敗,將會拋出異常
20、。MDN 引例如下:var invocation = new XMLHttpRequest();var url = http://bar.other/resources/credentialed-content/; function callOtherDomain(){ if(invocation) { invocation.open(GET, url, true); invocation.withCredentials = true; invocation.onreadystatechange = handler; invocation.send(); }}其實(shí)由上我們知道,CORS 的優(yōu)點(diǎn)也非常明顯:CORS支持所有類型的HTTP請求,是跨域HTTP請求的根本解決方案。以上就是所有的跨域請求解決方案,根據(jù)實(shí)際生產(chǎn)環(huán)境,總有一款適合你。參考