關於CORS 應該注意的幾點

前言

對於跨域,隨着w3c的CORS的出現,相比較於有些年頭的jsonp,CORS以其簡單安全,支持post的優點愈來愈收到你們的歡迎。具體如何CORS的原理和實現,直接推薦阮老師的文章,十分詳細。本文主要關注CORS實現過程當中的幾個疑惑點。html

預檢請求

背景

瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。前端

簡單請求

同時知足一下條件的便是簡單請求:java

  1. 請求方法是如下三種方法之一: HEAD、GET、POST
  2. HTTP的頭信息不超出如下幾種字段 Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值application/x-www-form、multipart/form-data、text/plain

非簡單請求

顯然,不一樣時知足則爲非簡單請求(能夠認爲是複雜請求)。二者的差異在於複雜請求在與服務端交互時多了一次options的預檢請求,畢竟複雜請求通常就是HTTP請求頭信息超出限制或者method爲put、delete等操做行爲,處於安全考慮,須要服務端先行驗證來決定是否給予相關權限。jquery

以下所示(示例來自阮老師文章):ios

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
// PUT method爲複雜請求,要預檢
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
複製代碼

非簡單請求,瀏覽器自動發送otpios的預檢請求,請求頭以下:nginx

OPTIONS /cors HTTP/1.1
// 請求源
Origin: http://api.bob.com
// 必須字段,指明正式cors請求將會使用那些method
Access-Control-Request-Method: PUT
// 除簡單頭以外,額外的請求頭
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
複製代碼

對於預檢信息,服務端通常作了以下操做:ajax

一、檢查origin、Access-Control-Request-Method和Access-Control-Request-Headers等字段,確認是否容許跨域,若是容許跨域做出迴應:shell

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
// 容許的源 
Access-Control-Allow-Origin: http://api.bob.com
// 容許的請求方式
Access-Control-Allow-Methods: GET, POST, PUT
// 容許額外header
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
複製代碼

若是不容許跨域,依然響應該請求,不過不攜帶CORS相關的信息。瀏覽器則會認爲服務器不容許跨域,觸發錯誤。json

// 常見的跨域錯誤
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
複製代碼

到這裏一個流程結束,不過咱們要關注的是options 預檢請求以後 code 返回的問題axios

options 成功以後,返回code 200 仍是 204

常規預檢的就是對於options的請求直接返回code 200的響應,表示校驗經過。 可是前兩天發現有的返回爲code204。二者之間的差異具體在哪呢。

常見用法

一、針對特定接口支持CORS時,在代碼里加判斷對於options返回200

// 隨便找了段java代碼 
if (req.getMethod().equals("OPTIONS")) {
     res.setStatus(200);
 }
複製代碼

二、若是整個域名都支持CORS,能夠再nginx側直接配置,此時常見的是返回204.

if ($request_method = 'OPTIONS') { 
    add_header Access-Control-Allow-Origin *; 
    add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
    #****省略...
    return 204; 
}
複製代碼

總結

二者之間的差異,首先能夠參考下204和200 對應的含義(下面內容摘自MDN)。 200 請求成功,成功的具體含義依據http method 的不一樣而有所差異。:

  • GET: 資源已經被提取並在消息中文中傳遞
  • POST: 描述動做結果的資源在消息體中傳輸

204 服務器成功處理了請求,但不須要返回任何實體內容,而且但願返回更新了的元信息。 客戶端是瀏覽器的haul,用戶瀏覽器應保留髮送了該請求的頁面,而不產生任何文檔視圖上的變化。因爲204響應被禁止包含任何消息體,所以它始終以消息頭後的第一個空行結尾。

簡單總結,204返回表示請求成功,而且無消息體,優點在於節省網絡請求。

具體到options請求,選用哪個。

貼切的來講,應該像其餘options請求同樣爲預檢optiosn請求返回相同的code狀態碼,相關規範不要求或者推薦其餘內容。
fecth請求 例如對於Fetch 規範 要求CORS協議的status能夠爲200-209裏面的任意值。

If a CORS check for request and response returns success 
and response’s status is an ok status, 
run these substeps.
複製代碼

若是response爲一個okstatus就能夠繼續執行

An ok status is any status in the range 200 to 299, inclusive.
複製代碼

並不要求具體哪個值。 因此從fetch來看,二者都可選擇。

HTTP 1.1 對於http/1.1 規範來講,有一章節專門定義了各類響應code。對於2開頭的2-XXcode,分別描述以下:

  • 200 請求成功,成功的具體含義依據http method 的不一樣而有所差異。
  • GET: 資源已經被提取並在消息中文中傳遞
  • POST: 描述動做結果的資源在消息體中傳輸
  • OPTIONS: communications options成功的表示 由上可知,對於options預檢請求的響應,須要包含下面兩種狀況: 一、代表請求成功 二、描述通訊選項(這裏包括, Access-Control-Allow-Methods 和 Access-Control-Allow-Headers這些響應頭) 看起來,上面就是200在http定義中的含義,顯然知足,可是若是繼續看204的含義,好像也能夠知足需求。

204 服務器成功處理了請求,但不須要返回任何實體內容,而且但願返回更新了的元信息。 客戶端是瀏覽器的話,用戶瀏覽器應保留髮送了該請求的頁面,而不產生任何文檔視圖上的變化。因爲204響應被禁止包含任何消息體,所以它始終以消息頭後的第一個空行結尾。

結論

首先二者均可以使用,對於200,從定義而言更符合場景和定義。可是204無消息體,優點在於節省網絡請求。
至於用哪一個,你們自行作下判斷。

跨域 讀取cookie

做爲常見的場景,cookie通常會存放一些,鑑權會話等信息。對於CORS跨域,默認的是不包含cookie的。

A cross-origin request by default does not bring any credentials (cookies or HTTP authentication)
複製代碼

若是要操做cookie須要分別從服務端和客戶端兩個場景來看。

客戶端 request 攜帶cookie

request若是要攜帶cookie,須要特定參數指明。可能看到過這個參數爲credentials或者withCredentials,何時用二者呢。主要跟請求的實現有關:

  1. Fetch 使用credentials 直接使用原生Fetch的話,須要設置credentials。

    credentials 是Request接口的只讀屬性,用於表示用戶代理是否應該在跨域請求的狀況下從其餘域發送cookies。這與XHR的withCredentials 標誌類似,不一樣的是有三個可選值(後者是兩個):

  • omit: 從不發送cookies.
  • same-origin: 只有當URL與響應腳本同源才發送 cookies、 HTTP Basic authentication 等驗證信息.(瀏覽器默認值,在舊版本瀏覽器,例如safari 11依舊是omit,safari 12已更改)
  • include: 不管是不是跨域的請求,老是發送請求資源域在本地的 cookies、 HTTP Basic authentication 等驗證信息.

CORS跨域的時候,只須要以下設置:

fetch('http://another.com', {
  credentials: "include"
});
複製代碼
  1. XHR 使用withCredentials 基於XMLHttpRequest實現的請求使用withCredentials來容許攜帶cookie。
    該屬性爲boolean類型,因此只有true/false兩個取值,默認爲false。
    這樣也很好理解,默認不攜帶是處於安全考慮。
    使用以下
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
複製代碼

適用框架:jquery的ajax,axios等。

服務端 Access-Control-Allow-Credentials

當客戶端設置了容許攜帶cookie以後,並不能完成該操做,畢竟是跨域,服務端也須要作響應設置,不然瀏覽器拿不到正確響應。

Access-Control-Allow-Credentials:true
複製代碼

看MDN 的解釋:

The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to frontend JavaScript code when the request's credentials mode (Request.credentials) is "include".  
複製代碼

當 credentials爲include的時候,通知瀏覽器是否將響應暴露給前端jscode,若是爲false,js不能讀取響應天然請求報錯。 只有Access-Control-Allow-Credentials爲true時,纔會將響應暴露給客戶端。 看成爲預檢請求響應頭時,代表該實際請求(即後面的真正請求)是否可使用credentials。

不過對於簡單請求,由於沒有預檢,若是服務端沒有正確響應,瀏覽器會忽略該屬性,並不會直接報錯。
須要與XMLHttpRequest.withCredentials屬性或者Fetch 的credentials 配合使用。

注意

若是要發送Cookie,Access-Control-Allow-Origin就不能設爲星號,必須指定明確的、與請求網頁一致的域名。
同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳。
且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。

畢竟cookie是有path來保證封閉性的,若是能夠隨便讀取無論從安全仍是性能上都是一種隱患。

多域名跨域

對於多域名跨域,方法比較多。

一、Access-Control-Allow-Origin:*

容許任意域名跨域,顯然支持多域名。不過從安全性和cookie的使用的角度來看並不推薦。

二、動態匹配域名

這種實現方式比較多,原理就是聲明容許的多域名配置,能夠是數組或者是正則,根據當前請求的域名,來判斷是否在適用返回內,在的話則設置Access-Control-Allow-Origin爲當前域名。

具體實現這裏就不寫了。

結束語

參考文章

www.ruanyifeng.com/blog/2016/0…
fetch.spec.whatwg.org/#cors-proto…
www.yunweipai.com/archives/93… 以上是在工做中偶然發現的幾點疑惑,解決以後深究了下具體原理。但願能對其餘同窗有所幫助,拋磚引玉,一塊兒努力。

相關文章
相關標籤/搜索