本人是一個應屆生,面試的時候經常會被問到跨域的問題,CORS當然也是解決跨域的方法之一了。但是當面試官繼續問:“CORS跨域是怎麼實現的?為什麼會有OPTIONS請求呢?OPTIONS請求有什麼用途呢?”可能回答的就不是那麼完美。
所以,就總結歸納了以下關於CORS的詳細知識。。。
參考文章:
跨域資源共享CORS詳解
為什麼會有OPTIONS請求
瀏覽器同源策略及其規避方法
HTTP訪問控制(CORS)
什麼是CORS?
CORS
是W3C標準,全稱“跨域資源共享(Corss-orign resource sharing)”。
它允許瀏覽器向跨源伺服器傳送XMLHttpRequest
請求,從而克服了AJAX
只能同源使用的限制。
CORS
需要瀏覽器和伺服器同時支援;整個CORS
的通訊過程都是瀏覽器自動完成的,使用者不需要參與;CROS
通訊和同源的AJAX
通訊無差別;瀏覽器檢測到AJAX
請求跨源時會自動新增一些附加的頭資訊有時候還會多一次附加請求,但是使用者不會有感覺。
CORS
通訊的關鍵還是伺服器,只要伺服器實現了CORS
的介面,就可以跨源通訊。
CORS分為兩類
CORS請求分為兩大類:簡單請求和非簡單請求,瀏覽器對這兩種請求的處理是不一樣的。
滿足以下條件就是簡單請求:
- 請求方法:
GET
,POST
,HEAD
- HTTP頭資訊不超過以下幾種:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:application/x-www-form-urlencode、mulitpart/form-data、text/plain(只限於這三個值)
簡單請求
對於簡單請求,瀏覽器會直接發出CORS
請求;瀏覽器發現此次跨源的AJAX
請求是簡單請求,就自動在請求頭資訊中新增Origin
欄位來說明本次請求來自哪個源(域名+埠+協議);伺服器根據這個值來決定是否同意這次請求。
如果Origin
指定的源不在許可的範圍內:
伺服器會返回一個正常的HTTP響應,但是這個響應的頭資訊沒有包含Access-Control-Allow-Origin
欄位,會丟擲錯誤,被XMLHttpRequest
的onerror
回撥函式捕獲(!!這種情況狀態碼無法識別,返回的很可能是200
)。
如果Origin
指定的源在許可的範圍內:
伺服器返回的響應會多幾個欄位:
- Access-Control-Allow-Origin:必須!要麼是個域名,要麼是個*,表示接受任何域名的請求。
- Access-Control-Allow-Credentials:可選!是一個布林值,表示允許傳送
cookie
;預設情況下,cookie
不包含在CORS
請求中。該欄位設為true
時,表示伺服器許可,cookie
可以包含在請求中,一起傳送給伺服器。 - Access-Control-Expose-Headers:可選!
CORS
請求時,XMLHttpRequest
物件的getResponseHeader()
只能拿到6個基本欄位,Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
。如果想拿到其他欄位就要在Access-Control-Expose-Headers
中指定,getResponseHeader('FooBar')
,就可以返回FooBar
的值。
withCredentials屬性
上面說到CORS
請求預設不會傳送cookie
和HTTP
的認證資訊,所以要把cookie
傳送到伺服器不但要伺服器同意設定
Access-Control-Allow-Credentials:true 複製程式碼
還要開發者在AJAX請求中設定withCredentials
屬性
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
複製程式碼
否則,即使伺服器同意傳送cookie
,瀏覽器也不會傳送。!!!如果要傳送cookie
,那麼Access-Control-Allow-Origin
的值不能是*
,必須是指定明確的,與請求網頁一致的域名;同時,cookie
依然遵循同源策略,只有用伺服器域名設定的cookie
才會上傳,其他域名的cookie
不會上傳,且(跨源)原網頁程式碼中document.cookie
也無法讀取伺服器域名下的cookie
。
非簡單請求
當滿足下面任意條件時,會傳送預檢請求:
- 使用了下面的任意方法:
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- 人為設定了對CORS安全的首部欄位集合的其他首部欄位,該集合有:
- Accept
- Accept-Language
- Content-Language
- Content-Type(but note the addtional requirements below)
- DPR
- Downlink
- Save-Data
- Viewport-width
- Width
- Content-Type的值不屬於下列之一:
- application/x-www-form-urlencode
- mulitpart/form-data
- text/plain
預檢請求Preflighted Requests
非簡單請求的CORS
請求,會在正式通訊之前,增加一次HTTP
查詢請求(‘預檢’請求)。preflighted requests
是CORS
中一種透明的伺服器驗證機制。預檢請求首先會向另一個域名資源傳送HTTP
OPTIONS
請求頭,來驗證傳送的請求是否安全。
瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器許可的名單之中,以及可以使用哪些HTTP
動詞和頭資訊欄位。只有得到肯定的答覆,瀏覽器才會發出正式的XMLHttpRequest
請求,否則會報錯。
瀏覽器發現,是一個非簡單請求,就自動傳送一個“預檢”請求,要求伺服器確認。
預檢請求的請求方法是OPTIONS
,表示詢問;頭資訊裡的關鍵字是Origin
,表示請求來自哪個源除了Origin
還有兩個欄位:
- Access-Control-Request-Method:列出瀏覽器的CORS請求會用到哪些HTTP方法。
- Access-Control-Request-Headers:指定瀏覽器CORS請求會額外傳送的頭資訊欄位。
OPTIONS請求的用途?
1、獲取伺服器支援的HTTP請求方法。2、用來檢查伺服器的效能。AJAX進行跨域請求時的預檢,需要向另一個域名的資源傳送一個HTTP OPTIONS的請求頭,用以判斷實際的請求是否安全。(這個是瀏覽器加上的,後端沒有做任何操作)複製程式碼
為什麼會有OPTIONS請求?
規範要求,對那些可能對伺服器資料產生副作用的HTTP
請求方法(特別是GET
以外的HTTP
請求,或搭配某些MIME
型別的POST
請求),瀏覽器必須首先使用OPTIONS
傳送一個預檢請求,來獲取伺服器是否允許該跨域請求。伺服器確認允許跨域後,瀏覽器再傳送實際的HTTP
請求。
為什麼沒有發生預檢請求?
在跨域請求伺服器時,設定了Content-Type
為application/json
,後,按理說,應該是會發起預檢請求。可實際結果與理論發生了衝突。
原因在於:預檢請求需要在伺服器中進行配置,在修改該路由的程式碼為以下內容後,瀏覽器正確地發起了OPTIONS預檢請求。
const cors=require('cors');
router.options('/api/test/corsopt',cors());
router.post('/api/test/corsopt',cors(),(req,res)=>
{
res.end('test');
})複製程式碼
預檢請求的回應
伺服器接受了預檢請求以後,會檢查Origin
,Access-Control-Request-Methods
,Access-Control-Request-Headers
欄位以後,確認允許跨域請求,就會做出回應。
如果瀏覽器否定了預檢請求,會返回一個正常的HTTP響應,但是沒有任何CORS相關的頭資訊欄位。這時瀏覽器就會認為伺服器不同意預檢請求,因此觸發一個錯誤,被XMLHttpRequest
物件的onerror
函式捕獲。該錯誤資訊為:
XMLHttpRequest cannot load http://xxx.xxx.com. Origin http://xxx.xxx.com is not allowed by Access-Control-Allow-Origin.複製程式碼
伺服器響應的其他CORS相關欄位:
- Access-Control-Request-Methods:列出瀏覽器的CORS請求會用到哪些HTTP方法(返回的是所有支援的方法)
- Access-Control-Request-Headers:表示伺服器支援的所有頭資訊欄位,不限於瀏覽器在“預檢”請求的欄位。
- Access-Control-Allow-Credential:與簡單請求的意義相同。
- Access-Control-Max-Age:可選!本次預檢請求的有限期,該期限期間不會再傳送預檢請求。
瀏覽器的正常請求和回應
一旦伺服器通過了“預檢”請求,以後每次瀏覽器正常的CORS請求,都和簡單請求一樣,會有一個Origin
頭資訊欄位;伺服器響應也是一樣會有一個Access-Control-Allow-Origin
頭資訊欄位。