詳細內容看這篇文檔,官方描述:Cross-Origin Resource Sharing (CORS):https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
我們在開發網站時經常會用到跨域資源共享(簡稱cors)來解決跨域問題,但是在使用cors的時候,http請求會被划分為兩類,簡單請求和復雜請求,而這兩種請求的區別主要在於是否會觸發cors預檢請求。
首先我們要明白cors的原理(引自MDN):
跨域資源共享標准新增了一組 HTTP 首部字段,允許服務器聲明哪些源站通過瀏覽器有權限訪問哪些資源。
另外,規范要求,對那些可能對服務器數據產生副作用的 HTTP 請求方法(特別是
GET
以外的 HTTP 請求,或者搭配某些 MIME 類型的POST
請求),瀏覽器必須首先使用OPTIONS
方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。服務器確認允許之后,才發起實際的 HTTP 請求。在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關數據)。
在涉及到CORS的請求中,我們會把請求分為簡單請求和復雜請求。
從上面的文字中我們得到如下信息:
1、跨域資源共享標准新增了一組 HTTP 首部字段,服務器通過這些字段來控制瀏覽器有權訪問哪些資源。
2、為了安全起見請求方式分為兩類,一類不會預先發送options請求,一些會預先發送options請求。
3、 GET
以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST
請求會觸發options請求。
4、服務器驗證OPTIONS完成后才會允許發送復雜的http請求。
不會觸發http預檢請求的便是簡單請求,相反能夠觸發http預檢請求的便是復雜請求。
一、什么是簡單請求
那么有哪些簡單請求呢?以下是來自MDN官方引用:
1、請求方法是以下三種方法之一:GET、POST、HEAD。
2、不能自定義請求頭header,不得人為設置該集合之外的其他首部字段。該集合為:Accept、Accept-Language、Content-Language、Content-Type
3、Content-Type 的值僅限於下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-http-urlencoded
4、請求中的任意XMLHttpRequestUpload 對象均沒有注冊任何事件監聽器;XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload 屬性訪問
5、請求中沒有使用 ReadableStream 對象
簡單請求的發送從代碼上來看和普通的XHR沒太大區別,但是HTTP頭當中要求總是包含一個域(Origin)的信息。該域包含協議名、地址以及一個可選的端口。不過這一項實際上由瀏覽器代為發送,並不是開發者代碼可以觸及到的。
簡單請求的部分響應頭及解釋如下:
Access-Control-Allow-Origin(必含)- 不可省略,否則請求按失敗處理。該項控制數據的可見范圍,如果希望數據對任何人都可見,可以填寫"*"。
Access-Control-Allow-Credentials(可選) – 該項標志着請求當中是否包含cookies信息,只有一個可選值:true(必為小寫)。如果不包含cookies,請略去該項,而不是填寫false。這一項與XmlHttpRequest2對象當中的withCredentials屬性應保持一致,即withCredentials為true時該項也為true;withCredentials為false時,省略該項不寫。反之則導致請求失敗。
Access-Control-Expose-Headers(可選) – 該項確定XmlHttpRequest2對象當中getResponseHeader()方法所能獲得的額外信息。通常情況下,getResponseHeader()方法只能獲得如下的信息:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
當你需要訪問額外的信息時,就需要在這一項當中填寫並以逗號進行分隔
二、什么是復雜請求
非簡單請求即為復雜請求。復雜請求我們也可以稱之為在實際進行請求之前,需要發起預檢請求的請求。
復雜請求在正式請求前都會有預檢請求,在瀏覽器中都能看到有OPTIONS請求,用於向服務器請求權限信息的。如果僅僅是簡單請求,那么即便不用CORS也沒有什么大不了,但CORS的復雜請求就令CORS顯得更加有用了。簡單來說,任何不滿足上述簡單請求要求的請求,都屬於復雜請求。比如說你需要發送PUT、DELETE等HTTP動作,或者發送Content-Type: application/json的內容。
復雜請求表面上看起來和簡單請求使用上差不多,但實際上瀏覽器發送了不止一個請求。其中最先發送的是一種"預請求",此時作為服務端,也需要返回"預回應"作為響應。預請求實際上是對服務端的一種權限請求,只有當預請求成功返回,實際請求才開始執行。
預請求以OPTIONS形式發送,當中同樣包含域,並且還包含了兩項CORS特有的內容
Access-Control-Request-Method – 該項內容是實際請求的種類,可以是GET、POST之類的簡單請求,也可以是PUT、DELETE等等。
Access-Control-Request-Headers – 該項是一個以逗號分隔的列表,當中是復雜請求所使用的頭部。
顯而易見,這個預請求實際上就是在為之后的實際請求發送一個權限請求,在預回應返回的內容當中,服務端應當對這兩項進行回復,以讓瀏覽器確定請求是否能夠成功完成。
復雜請求的部分響應頭及解釋如下:
Access-Control-Allow-Origin(必含) – 和簡單請求一樣的,必須包含一個域。
Access-Control-Allow-Methods(必含) – 這是對預請求當中Access-Control-Request-Method的回復,這一回復將是一個以逗號分隔的列表。盡管客戶端或許只請求某一方法,但服務端仍然可以返回所有允許的方法,以便客戶端將其緩存。
Access-Control-Allow-Headers(當預請求中包含Access-Control-Request-Headers時必須包含) – 這是對預請求當中Access-Control-Request-Headers的回復,和上面一樣是以逗號分隔的列表,可以返回所有支持的頭部。這里在實際使用中有遇到,所有支持的頭部一時可能不能完全寫出來,而又不想在這一層做過多的判斷,沒關系,事實上通過request的header可以直接取到Access-Control-Request-Headers,直接把對應的value設置到Access-Control-Allow-Headers即可。
Access-Control-Allow-Credentials(可選) – 和簡單請求當中作用相同 Access-Control-Max-Age(可選) – 以秒為單位的緩存時間。預請求的的發送並非免費午餐,允許時應當盡可能緩存。
XMLHttpRequest會遵守同源策略(same-origin policy),也即腳本只能訪問相同協議、相同主機名、相同端口的資源,如果要突破這個限制,那就是所謂的跨域,此時需要遵守CORS(Cross-Origin Resource Sharing)機制。
那么允許跨域,不就是服務端設置:Access-Control-Allow-Origin: *, 就可以了嗎? 普通的請求才是這樣子的, 除此之外,還一種叫請求叫preflighted request。
preflighted request在發送真正的請求前, 會先發送一個方法為OPTIONS的預請求(preflight request), 用於試探服務端是否能接受真正的請求,如果options獲得的回應是拒絕性質的,比如404\403\500等http狀態,就會停止post、put等請求的發出。
第一個OPTIONS的請求是由Web服務器處理跨域訪問引發的。OPTIONS是一種“預檢請求”,瀏覽器在處理跨域訪問的請求時如果判斷請求為復雜請求,則會先向服務器發送一條預檢請求,根據服務器返回的內容瀏覽器判斷服務器是否允許該請求訪問。如果web服務器采用cors的方式支持跨域訪問,在處理復雜請求時這個預檢請求是不可避免的。
三、簡單請求與復雜請求的跨域設置
針對簡單請求,在進行CORS設置的時候,我們只需要設置
Access-Control-Allow-Origin:*
// 如果只是針對某一個請求源進行設置的話,可以設置為具體的值
Access-Control-Allow-Origin: 'http://www.***.com'
針對復雜請求,我們需要設置不同的響應頭。因為在預檢請求的時候會攜帶相應的請求頭信息
Access-Control-Request-Method: POST Access-Control-Request-Headers: X-CUSTOMER-HEADER, Content-Type
相應的響應頭信息為:
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 設置max age,瀏覽器端會進行緩存。沒有過期之前真對同一個請求只會發送一次預檢請求
Access-Control-Max-Age: 86400
如果發送的預檢請求被進行了重定向,那大多數的瀏覽器都不支持對預檢請求的重定向。我們可以通過先發送一個簡單請求的方式,獲取到重定向的url XHR.responseURL,然后再去請求這個url。
1、附帶身份憑證的請求
一般而言,對於跨域 XMLHttpRequest
或 Fetch 請求,瀏覽器不會發送身份憑證信息。如果要發送憑證信息,需要設置 XMLHttpRequest 的某個特殊標志位。
如果在發送請求的時候,給xhr 設置了withCredentials為true,從而向服務器發送 Cookies,如果服務端需要想客戶端也發送cookie的情況,需要服務器端也返回Access-Control-Allow-Credentials: true
響應頭信息。
對於附帶身份憑證的請求,服務器不得設置 Access-Control-Allow-Origin
的值為“*
”。
這是因為請求的首部中攜帶了Cookie
信息,如果 Access-Control-Allow-Origin
的值為“*
”,請求將會失敗。而將 Access-Control-Allow-Origin
的值設置為 http://foo.example
(請求源),則請求將成功執行。