詳解瀏覽器跨域的幾種方法

thlzjfefe發表於2020-12-19

摘要:本文針對瀏覽器的跨域特性,做一下深入介紹,以便我們在進行WEB前端開發和測試時,對瀏覽器跨域特性有全面的理解和掌握。
1 前言
在WEB前端開發中,我們經常會碰到“跨域”問題,最常見的就是瀏覽器在A域名頁面傳送B域名的請求時會被限制。跨域問題涉及到WEB網頁安全性問題,使用不當會造成使用者隱私洩露風險,但有時業務上又需要進行跨域請求。如何正確的使用跨域功能,既能滿足業務需求,又能夠滿足安全性要求,顯得尤為重要。

本文針對瀏覽器的跨域特性,做一下深入介紹,以便我們在進行WEB前端開發和測試時,對瀏覽器跨域特性有全面的理解和掌握。

2 背景知識介紹
2.1 同源政策

1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實行這個政策。

最初,它的含義是指,A 網頁設定的 Cookie,B 網頁不能開啟,除非這兩個網頁“同源”。所謂“同源”指的是“三個相同”:

協議相同
域名相同
埠相同
同源政策的目的,是為了保證使用者資訊的安全,防止惡意的網站竊取資料。

設想這樣一種情況:

A 網站是一家銀行,使用者登入以後,A 網站在使用者的機器上設定了一個 Cookie,包含了一些隱私資訊(比如存款總額)。使用者離開 A 網站以後,又去訪問 B 網站,如果沒有同源限制,B 網站可以讀取 A 網站的 Cookie,那麼隱私資訊就會洩漏。更可怕的是,Cookie 往往用來儲存使用者的登入狀態,如果使用者沒有退出登入,其他網站就可以冒充使用者,為所欲為。因為瀏覽器同時還規定,提交表單不受同源政策的限制。由此可見,同源政策是必需的,否則 Cookie 可以共享,網際網路就毫無安全可言了。

當前,如果非同源,共有三種行為受到限制:

Cookie、LocalStorage 和 IndexDB 無法讀取
DOM 無法獲得
AJAX 請求不能傳送
2.2 為什麼要有跨域限制

Ajax 的同源策略主要是為了防止 CSRF(跨站請求偽造) 攻擊,如果沒有 AJAX 同源策略,相當危險,我們發起的每一次 HTTP 請求都會帶上請求地址對應的 cookie,那麼可以做如下攻擊:

使用者登入了自己的銀行頁面 http://mybank.com,http://mybank.com向使用者的cookie中新增使用者標識。
使用者瀏覽了惡意頁面 http://evil.com。執行了頁面中的惡意AJAX請求程式碼。
http://evil.com向http://mybank.com發起AJAX HTTP請求,請求會預設把http://mybank.com對應cookie也同時傳送過去。
銀行頁面從傳送的cookie中提取使用者標識,驗證使用者無誤,response中返回請求資料。此時資料就洩露了。
而且由於Ajax在後臺執行,使用者無法感知這一過程。
DOM同源策略也一樣,如果 iframe 之間可以跨域訪問,可以這樣攻擊:

做一個假網站,裡面用iframe巢狀一個銀行網站 http://mybank.com。
把iframe寬高啥的調整到頁面全部,這樣使用者進來除了域名,別的部分和銀行的網站沒有任何差別。
這時如果使用者輸入賬號密碼,我們的主網站可以跨域訪問到http://mybank.com的dom節點,就可以拿到使用者的輸入了,那麼就完成了一次攻擊。
所以有了跨域訪問限制之後,我們才能夠安全的上網。

3 瀏覽器跨域的解決方案
3.1 CORS標準

CORS 是一個 W3C 標準,全稱是跨域資源共享(CORSs-origin resource sharing),它允許瀏覽器向跨源伺服器,發出XMLHttpRequest請求。

其實,準確的來說,跨域機制是阻止了資料的跨域獲取,不是阻止請求傳送。

CORS需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10。

https://caniuse.com/#search=cors

整個CORS通訊過程,都是瀏覽器自動完成,不需要使用者參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動新增一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。

因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨域通訊。

3.2 CORS跨域判定的總體流程

如圖所示,跨域的判定流程為:

網頁上的JS程式碼,從瀏覽器上傳送XMLHttpRequest請求到服務端
如果該請求為簡單請求,瀏覽器會直接傳送實際請求到服務端,瀏覽器會根據服務端的響應,判斷該請求是否可以跨域:
(1)如果不能跨域,瀏覽器會報錯,阻止JS程式碼進一步執行;

(2)如果能夠跨域,則JS能正常處理響應,進行後續業務流程

如果該請求為非簡單請求,瀏覽器會先傳送一個預檢請求(preflight),方法為OPTIONS,然後針對伺服器的響應,做上述跟簡單請求一樣相同的判斷:
(1)如果不能跨域,則實際請求不會傳送

(2)如果能夠跨域,則實際請求會進行傳送,進行後續業務處理

值得說明的是,瀏覽器在跨域的情況下,請求都會傳送出去,但是對於響應會判斷是否滿足跨域條件,如果不滿足,則報錯,阻止JS後續的執行流程,例如讀取響應資料等。也就是說,跨域機制主要是阻止資料的跨域獲取,不是阻止請求的傳送。

3.3 簡單請求

實際上瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

只要同時滿足以下條件,就屬於簡單請求,一般來說,只需要滿足前兩個即可:

請求方法是如下三種方法之一:GET、POST、HEAD
HTTP訊息頭不超過如下幾個欄位:
Accept

Accept-Language

Content-Language

Last-Event-ID

Content-Type:只限於三個值:application/x-www-form-urlencoded、multipart/form-data、text/plain

請求中的任意XMLHttpRequestUpload 物件均沒有註冊任何事件**器
XMLHttpRequestUpload 物件可以使用 XMLHttpRequest.upload 屬性訪問。請求中沒有使用 ReadableStream 物件
對於簡單請求,瀏覽器會直接發起CORS請求,將實際請求發給伺服器,伺服器返回響應給瀏覽器,同時在響應頭域中攜帶CORS相關頭域,供瀏覽器進行跨域判斷。

3.4 非簡單請求

非簡單請求時指那些對伺服器有特殊要求的請求,比如請求方法是 PUT或 DELETE,或者 Content-Type 的型別是 application/json。簡而言之,不是簡單請求的HTTP請求,都是非簡單請求。

非簡單請求的 CORS 請求,會在正式通訊之前,使用 OPTIONS 方法發起一個預檢(preflight)請求到伺服器,瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些 HTTP方法和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的 XMLHttpRequest 請求,否則就報錯。

3.5 CORS相關頭域

那麼,無論是簡單請求,還是非簡單請求,瀏覽器都會對響應頭域中的CORS相關欄位進行判斷,CORS的常見欄位有如下幾個:

3.5.1 Access-Control-Allow-Origin(必選)

涉及簡單Http請求、非簡單Http請求

含義:允許的域名,只能填 *(萬用字元)或者單域名

舉例:

從https://www.huaweicloud.com網頁,傳送https://portal.huaweicloud.com請求,如果伺服器響應頭域中沒有填寫Access-Control-Allow-Origin,瀏覽器會報錯:

或者取值不為https://www.huaweicloud.com,瀏覽器也會報錯:

填寫為*或者https://www.huaweicloud.com,則不會報錯:

3.5.2 Access-Control-Allow-Credentials(可選)

涉及簡單Http請求、非簡單Http請求

含義:表示是否允許傳送Cookie,只有一個可選值:true(必為小寫)。如果不包含cookies,請略去該項,而不是填寫false。這一項與 XmlHttpRequest 物件當中的 withCredentials 屬性應保持一致,即 withCredentials 為true時該項也為true;withCredentials 為false時,省略該項不寫。反之則導致請求失敗。

舉例:

當XmlHttpRequest中設定了withCredentials為true,如果伺服器響應裡沒有Access-Control-Allow-Credentials欄位,則瀏覽器會報錯:

特別的,當XmlHttpRequest中設定了withCredentials為true時,還要求Access-Control-Allow-Origin欄位不能為萬用字元*,其實這也好理解,因為設定了withCredentials,表示允許跨域傳送Cookie,如果Origin允許為*的話,安全性就會大大降低了,很容易構造跨站攻擊:

3.5.3 Access-Control-Expose-Headers(可選)

涉及簡單Http請求、非簡單Http請求

含義:CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。

舉例:

在JS程式碼中,通過XMLHttpRequest物件來獲取響應中的wise_traceid頭域,如:

xhr. getResponseHeader(“wise_traceid”)

如果在伺服器響應中,沒有攜帶Access-Control-Expose-Headers或者Access-Control-Expose-Headers的值不包含wise_traceid,則瀏覽器會報錯,JS拿到的值也是null:

3.5.4 預檢請求preflight

根據上述分析,如果是非簡單Http請求,瀏覽器會先傳送一個預檢請求,要求伺服器進行確認。預檢請求使用的方法是OPTIONS,表示這個請求是用來詢問的,頭資訊裡面,關鍵欄位是Origin,表示請求來自哪個源。

除了Origin欄位,"預檢"請求的頭資訊包括兩個特殊欄位:

Access-Control-Request-Method
該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,如PUT:

Access-Control-Request-Headers
該欄位是一個逗號分隔的字串,指定瀏覽器CORS請求會額外傳送的頭資訊欄位,當使用了簡單請求那5個欄位之外的欄位,瀏覽器會在OPTIONS請求頭域中,指定 Access-Control-Request-Headers的取值,如:

如果預檢請求的響應,瀏覽器沒有校驗通過,不允許跨域,瀏覽器除了會在控制檯報錯之外,後續實際請求也不會傳送了。

3.5.5 Access-Control-Allow-Methods(必選)

涉及非簡單Http請求

含義:允許跨域請求的 http 方法(如POST、GET、OPTIONS、PUT、DELETE),該欄位是對於預檢請求中的Access-Control-Request-Method的回覆。

備註:對於簡單請求的GET、POST方法,該欄位不是必選的,瀏覽器會預設允許這兩個方法進行跨域

舉例:

從https://www.huaweicloud.com,跨域訪問https://portal.huaweicloud.com,HTTP方法為POST,如果伺服器響應裡沒有Access-Control-Allow-Methods,跨域請求能夠成功:

如果伺服器響應裡Access-Control-Allow-Methods不包含POST,跨域請求也能成功:

如果請求為PUT方法,但響應裡沒有攜帶Access-Control-Allow-Methods或者取值不包含PUT,瀏覽器會報錯:

3.5.6 Access-Control-Allow-Headers(可選)

涉及非簡單Http請求

含義:該欄位指定了跨域允許設定的非簡單Http請求頭(5個簡單Http請求頭之外的頭域),(當預請求中包含 Access-Control-Request-Headers 時必須包含)– 這是對預請求當中 Access-Control-Request-Headers 的回覆,和上面一樣是以逗號分隔的列表,可以返回所有支援的頭部。

舉例:

如果在XMLHttpRequest中設定了wise_groupid欄位,而伺服器響應中,沒有Access-Control-Allow-Headers頭域,或者Access-Control-Allow-Headers頭域的值不包含wise_groupid,則瀏覽器會報錯:

3.5.7 Access-Control-Max-Age(可選)

涉及簡單Http請求、非簡單Http請求

含義:用來指定本次預檢請求的有效期,單位為秒。例如,Access-Control-Max-Age被設定為1728000,表示有效期是20天(1728000秒),即允許快取該條回應1728000秒(即20天),在此期間,不用發出另一條預檢請求。

4 其它跨域手段
4.1 JSONP跨域

在HTML文件中,有一個script標籤,該標籤一般會引用當前HTML文件需要載入的js指令碼,例如:

瀏覽器在載入HTML文件時,會順序載入script標籤中src的地址,這種載入是可以跨域載入的,瀏覽器不會阻止跨域的js載入。

此外,還有img等標籤,可以跨域載入。

而 JSONP正是利用了script/img等標籤能夠跨域載入,來實現跨域請求的功能,如下程式碼所示:

程式碼先定義一個全域性函式,然後把這個函式名通過callback引數新增到script標籤的src,script的src就是需要跨域的請求,然後這個請求返回可執行的JS文字:

由於它是一個js,並且已經定義了upldateList函式,所以能正常執行,並且跨域的資料通過傳參得到。這就是JSONP的原理。

如下圖所示,伺服器返回了響應之後,js方法updateList就可以獲取到響應內容,列印在控制檯:

JSONP方式跨域訪問的時候,還會攜帶域名的Cookie:

從上面可以看到,JSONP方式跨域,會不受同源政策影響,並且會攜帶跨域域名的Cookie,同樣也會存在安全風險。

由於JSONP是利用script/img等標籤來實現跨域,而瀏覽器載入這些標籤,使用的是GET方法,這就要求業務對於一些重要的請求,不能夠使用GET方法提交資料,必須要使用POST方法,這樣就無法利用JSONP進行跨域請求了。

4.2 服務端轉發

如上圖所示,服務端轉發實現跨域訪問的基本原理是,將訪問B域名的請求,通過訪問A域名,由A伺服器轉發給B伺服器:

舉例:

在https://www.huaweicloud.com頁面上,想要訪問https://portal.huaweicloud.com/v1/template介面,那麼可以通過如下手段實現跨域:

頁面上呼叫https://www.huaweicloud.com/portal/v1/template介面
www.huaweicloud.com伺服器,對於/portal開頭的請求,統一去掉portal路徑,轉發給portal.huaweicloud.com伺服器,相當於www.huaweicloud.com伺服器做了反向代理
但是使用該方法,會導致無法傳送http://portal.huaweicloud.com域名下的cookie,因此應用場景有限。

5 擴充套件
5.1 為什麼要區分簡單請求和非簡單請求?

按照上文介紹:

簡單請求就是滿足方法是GET、POST、HEAD,頭域為Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type,且Content-Type只限於三個值:application/x-www-form-urlencoded、multipart/form-data、text/plain的請求,比如普通的提交HTML Form表單的請求。

非簡單請求,就是普通 HTML Form 無法實現的請求。比如 PUT 方法、需要其他的內容編碼方式、自定義頭之類的。

對於伺服器來說:

第一, 許多伺服器壓根沒打算給跨站訪問使用,不會給CORS響應頭,瀏覽器也會做相應報錯,但是由於跨域訪問不會阻止請求發出,但是請求本身可能已經造成了後果,所以最好能夠阻止跨站請求發出。

第二, 要回答某個請求是否接受跨域訪問,可能涉及額外的計算邏輯,這個邏輯可能簡單,如一律放通;但也可能複雜,可能取決於哪個資源、哪種操作、來自哪個Origin。對於瀏覽器來說,它只需要知道能否跨域訪問,但是對於伺服器來說,計算成本可大可小。所以我們希望這種判斷不需要每次由伺服器進行計算。

CORS的預檢請求preflight就是這樣一種機制,瀏覽器先單獨請求一次,詢問伺服器某個資源是否可以跨站訪問,如果不允許的話,就在預檢請求的響應中告知瀏覽器,使得瀏覽器不再傳送實際請求。

這個機制即為“先許可,再請求”,因此預設禁止了跨站請求。

如果允許的話,瀏覽器才會繼續傳送實際請求,這樣不合法跨站請求就不會對伺服器造成任何影響。

但是這種機制,只能限於非簡單請求。在處理簡單請求的時候,如果伺服器不打算接受跨站請求,不能依賴CORS預檢請求preflight機制,因為普通表單會直接發起實際請求,所以預設禁止跨站的簡單請求是做不到的。

因此,我們常在安全規範中看到,不要使用GET方法來提交重要敏感資料,不要使用簡單的表單請求來提交敏感資料等,原因就在這裡。

6 總結
瀏覽器跨域有三大方式,AJAX請求跨域、JSONP跨域、服務端轉發跨域,每種跨域會適用於不同的業務場景。

其中,AJAX跨域使用場景較多,遵循W3C標準,由瀏覽器和伺服器根據HTTP頭域Access-Control開頭的相關欄位協商處理跨域流程。

HTTP請求還分為簡單請求和非簡單請求,在非簡單請求的跨域訪問時,還會觸發預檢請求preflight流程。

對於我們業務開發和測試的啟示:

對於重要敏感資料,不要使用GET、簡單表單提交等簡單HTTP請求來處理,需要使用非簡單請求來處理,這樣就沒法通過JSONP等跨域手段來攻擊獲取敏感資料。

此外,除了不使用簡單請求之外,還可以通過每次請求使用不同隨機串、增加驗證碼方式二次校驗等方法,來防止請求被跨站偽造。

7 參考資料
[1] 瀏覽器同源政策及其規避方法

[2] 徹底理解瀏覽器的跨域

[3] 為什麼跨域的post請求區分為簡單請求和非簡單請求和content-type相關?

[4] 淺談CSRF跨域攻擊

深入理解瀏覽器跨域原理(演示程式).rar( 1.38 MB, 0 downs )

相關文章