瀏覽器跨域請求的機制:CORS
大家好,我是前端西瓜哥。
因為同源策略(Cross-Origin Policy)的存在,瀏覽器在一個域名下發送另一個域名的 Ajax 請求時,返回的數據通常會被瀏覽器攔截,讓開發者無法拿到返回結果。
這里說 “通常”,是因為瀏覽器額外提供了一種可以正常使用 Ajax 請求非同源域名接口的機制,也就是我們接下來要說的 跨源資源共享(CORS, Cross-Origin Resource Sharing)。
CORS 會在上圖中的第三步中發揮作用。
簡單來說,就是請求 b.com 的 HTTP 響應頭字段中的一些頭字段符合一定規則,返回數據就不會被瀏覽器攔截,請求者能正常獲得返回數據。
簡單請求和非簡單請求是瀏覽器發出跨域請求的 兩種不同的請求方式,我們來了解一下。
簡單請求
發送的請求符合下面的所有情況,就屬于簡單請求。
- 請求方法為其中一種:GET、POST、HEAD。
- 除了瀏覽器自動設置的字段(比如 Connection),僅能人為設置請求頭字段 Accept、Accept-Language、Content-Language、Content-Type 這個集合內的值。
- 請求頭字段 Content-Type 為其中一種:text/plain、 multipart/form-data、application/x-www-form-urlencoded。
(還有一些更多的瑣碎的細節,比如腳本設置、特定瀏覽器相關的,這里不展開,具體可以查閱官方文檔)
可能你會對這個規則的設計很感興趣,其實它是為了 向后兼容,兼容一些之前沒有 Ajax 時就可以發送的跨域請求,比如 form 元素能夠產生的請求。
點擊表單下的提交按鈕,頁面跳轉然后發送請求。這種寫法非常古老了,因為會自動跳轉頁面且不能拿到返回數據執行下一步的操作,實際開發基本上不會使用了
<form action="https://b.com/api/v1/book/get" method="get">
<input type="text" name="name" value="clean code" />
<input type="submit" value="提交"/>
</form>
CORS 機制下,HTTP 響應頭字段需要使用頭字段
Access-Control-Allow-Origin,將它的值設置為:
- *:任意域名都允許跨域請求。
- <origin>:具體的域名,這里不能提供多個域名,只能提供一個域名。這也是可以理解的,通過強制要求防止黑客得到服務端設置的白名單域名。我們不應該透露太多信息。服務端要實現跨域白名單功能,就需要根據請求頭字段中動態返回該字段的值。
另外,如果請求中攜帶了身份信息,也就是 Cookie,不能使用 *,而需要指定具體域名。
這樣設置后,我們就能正常地拿到其他域名接口返回的數據了。
下面我們再看看非簡單請求。
非簡單請求
不符合簡單請求規定條件的請求,就是 非簡單請求。
對于簡單請求,為了兼容,瀏覽器會直接將請求發出去。但非簡單請求沒有兼容的需要,所以瀏覽器給它加上了嚴格的復雜機制。
在發出真正的請求前,瀏覽器會先發一個 OPTION 請求來探探路,這個請求稱為 預檢請求(preflight)。
瀏覽器發送的請求,會額外帶下以下請求頭字段:
- Access-Control-Request-Method:該字段告知服務端接下來要發起真正跨域請求使用的 HTTP 方法。
- Access-Control-Request-Headers:該字段告知服務端,當前請求使用的不符合簡單請求規則的頭字段,比如使用 JSON 結構來作為數據的格式,預檢請求就會帶上上 Access-Control-Request-Headers: content-type
下面是服務端的回合。
服務端拿到了足夠多的請求方信息,然后就可以考慮是否允許跨域了。
服務端的響應頭字段可以攜帶上以下字段:
- Access-Control-Allow-Origin:用法同上一節的簡單請求說明。
- Access-Control-Allow-Methods:可以發送的請求方法。如 POST, GET, OPTIONS。
- Access-Control-Allow-Headers:可以使用的頭字段,如 X-PINGOTHER, Content-Type
- Access-Control-Max-Age: 86400:緩存的有效時長,單位為秒,設置后,在這段時間內都可以免除預檢請求的發送。瀏覽器自身預設了一個最大緩存時間,防止返回的緩存時間過長(比如好幾年)導致的安全問題。
瀏覽器拿到這些字段后,就會進行對比,如果不符合規則,那么真正的跨域請求將不會發送。如果不符合,接下來就會發送真正的跨域請求。
需要注意的是,真正的請求和簡單請求一樣,需要提供Access-Control-Allow-Origin 頭字段,否則依舊拿不到返回數據。
結尾
CORS 真正的形態是非簡單請求,它不會立即發送真正的請求,而是額外發送 OPTION 方法的預檢請求,讓服務端來決定是否允許即將發送的請求。
簡單請求是向后兼容的妥協產物,它其實直接向目標發起了真正請求,只是瀏覽器可能會將返回的數據攔截掉,如果響應頭沒有設置正確的
Access-Control-Allow-Origin 的話。