跨域問題是怎樣造成的?

跨域問題的由來

相信很多人都或多或少了解過跨域問題,尤其在現如今前後端分離大行其道的時候。

你在本地開發一個前端項目,這個項目是通過 node 運行的,端口是9528,而服務端是通過 spring boot 提供的,端口號是7001。

當你調用一個服務端接口時,很可能得到類似下面這樣的一個錯誤:

跨域問題是怎樣造成的?

然後你在發送請求的地方debug,在出現異常的地方你將得到這樣的結果:

跨域問題是怎樣造成的?

異常對象很詭異,返回的 response 是 undefined 的,並且 message 消息中只有一個"Network Error"。

看到這裡你應該要知道,你遇到跨域問題了。

但是你需要明確的一點是,這個請求已經發出去了,服務端也接收到並處理了,但是返回的響應結果不是瀏覽器想要的結果,所以瀏覽器將這個響應的結果給攔截了,這就是為什麼你看到的response是undefined。

瀏覽器的同源策略

那瀏覽器為什麼會將服務端返回的結果攔截掉呢?

這就需要我們瞭解瀏覽器基於安全方面的考慮,而引入的 同源策略(same-origin policy) 了。

早在1995年,Netscape 公司就在瀏覽器中引入了“同源策略”。

最初的 “同源策略”,主要是限制Cookie的訪問,A網頁設置的 Cookie,B網頁無法訪問,除非B網頁和A網頁是“同源”的。

那麼怎麼確定兩個網頁是不是“同源”呢,所謂“同源”就是指"協議+域名+端口"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。

跨域問題是怎樣造成的?

沒有同源策略的保護

那麼為什麼要做這個同源的限制呢?因為如果沒有同源策略的保護,瀏覽器將沒有任何安全可言。

老李是一個釣魚愛好者,經常在 我要買(51mai.com) 的網站上買各種釣魚的工具,並且通過 銀行(yinhang.com) 以賬號密碼的方式直接支付。

這天老李又在 51mai.com 上買了一根魚竿,輸入銀行賬號密碼支付成功後,在支付成功頁看到一個叫 釣魚(diaoyu.com) 的網站投放的一個"免費領取魚餌"的廣告。

老李什麼都沒想就點擊了這個廣告,跳轉到了釣魚的網站,殊不知這真是一個 “釣魚” 網站,老李銀行賬戶裡面錢全部被轉走了。

跨域問題是怎樣造成的?

以上就是老李的錢被盜走的過程:

1.老李購買魚竿,並登錄了銀行的網站輸入賬號密碼進行了支付,瀏覽器在本地緩存了銀行的Cookie

2.老李點擊釣魚網站,釣魚網站使用老李登錄銀行之後的Cookie,偽造成自己是老李進行了轉賬操作。

這個過程就是著名的CSRF(Cross Site Request Forgery),跨站請求偽造,正是由於可能存在的偽造請求,導致了瀏覽器的不安全。

那麼如何防止CSRF攻擊呢,可以參考這篇文章:如何防止CSRF攻擊?

同源策略限制哪些行為

上面說了 **同源策略 **是一個安全機制,他本質是限制了從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互,這是一個用於隔離潛在惡意文件的重要安全機制。

隨著互聯網的發展,"同源策略"越來越嚴格,不僅限於Cookie的讀取。目前,如果非同源,共有三種行為受到限制。

(1) Cookie、LocalStorage 和 IndexDB 無法讀取。

(2) DOM 無法獲得。

(3) 請求的響應被攔截。

雖然這些限制是必要的,但是有時很不方便,合理的用途也會受到影響,所以為了能夠獲取非“同源”的資源,就有了跨域資源共享。

跨域資源共享

看到這裡你應該明白,為什麼文章開頭的請求會被攔截了,原因就是請求的源和服務端的源不是“同源”,而服務端又沒有設置允許的跨域資源共享,所以請求的響應被瀏覽器給攔截掉了。

CORS 是一個 W3C 標準,全稱是"跨域資源共享"(Cross Origin Resource Sharing),它允許瀏覽器向跨源服務器,發出 XMLHttpRequest 請求,從而克服了只能發送同源請求的限制。

CORS實現機制

那跨域資源共享機制是怎樣實現的呢?

當一個資源(origin)通過腳本向另一個資源(host)發起請求,而被請求的資源(host)和請求源(origin)是不同的源時(協議、域名、端口不全部相同),瀏覽器就會發起一個 跨域 HTTP 請求 ,並且瀏覽器會自動將當前資源的域添加在請求頭中一個叫 Origin 的 Header 中。

當然了,有三個標籤本身就是允許跨域加載資源的:

比如某個網站的首頁 http://domain-a.com/index.html 通過 來加載其他域上的圖片,除此之外還有諸如通過 CDN 節點引入css和js文件的方式。

出於安全原因,瀏覽器限制從腳本內發起的跨域 HTTP 請求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 也就是說使用這些 API 的 Web 應用程序只能從加載應用程序的同一個域請求 HTTP 資源,除非響應報文中包含了正確 CORS 響應頭。

通過在響應報文中設置額外的 HTTP 響應頭來告訴瀏覽器,運行在某個 origin 上的 Web 應用被准許訪問來自不同源服務器上的資源,此時瀏覽器就不會將該響應攔截掉了。

那這些額外的 HTTP 響應頭是什麼呢?

響應頭是否必須含義Access-Control-Allow-Origin是該字段表示,服務端接收哪些來源的域的請求Access-Control-Allow-Credentials否是否可以向服務端發送Cookie,默認是 falseAccess-Control-Expose-Headers否可以向請求額外暴露的響應頭

其中只有 Access-Control-Allow-Origin 是必須的,該響應頭的值可以是請求的 Origin 的值,也可以是 * ,表示服務端接收所有來源的請求。

當瀏覽器發起 CORS 請求時,默認只能獲得6個響應頭的值:

Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma

如果還需要返回其他的響應頭給前端,則可以通過在 Access-Control-Expose-Headers 中指定。

CORS的兩種請求類型

CORS有兩種類型的請求,分別是:簡單請求(simple request)和非簡單請求(not-so-simple request)

只要同時滿足以下兩大條件,就屬於簡單請求。

(1) 請求方法是以下三種方法之一:

HEADGETPOST

(2) HTTP的頭信息不超出以下幾種字段:

AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限於三個值 application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同時滿足上面兩個條件,就屬於非簡單請求,瀏覽器對這兩種請求的處理,是不一樣的。

為什麼會有兩種不同類型的請求呢?

CORS 規範要求,對那些可能對服務器數據產生副作用的 HTTP 請求方法(特別是

GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。

服務器確認允許之後,瀏覽器才能發起實際的 HTTP 請求。在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關的數據)。

非簡單請求就要求瀏覽器先發送一個預檢請求,預檢通過後再發送實際的請求。

怎樣實現CORS

知道了CORS的實現機制之後,我們就可以解決遇到的CORS的問題了。

1.通過JSONP

利用


分享到:


相關文章: