随着Web開放的程度越來越高,通過浏覽器跨域擷取資源的需求已經變得非常普遍。在我看來,如果Web
API不能針對浏覽器提供跨域資源共享的能力,它甚至就不應該被稱為Web
API。從另一方面來看,浏覽器作為進入Internet最大的入口,是各大IT公司的必争之地,是以浏覽器市場出現了種類繁多、魚龍混雜的局面。針對這兩點,我們迫切需要一種能夠被各個浏覽器廠商共同遵循的标準來對跨域資源共享作出規範,這就是由W3C指定2的CORS(Cross-Origin
Resource Sharing)規範。
目錄 CORS是如何工作的? 對響應報頭的授權 預檢機制 是否支援使用者憑證
基于Web的資源共享涉及到兩個基本的角色,即資源的提供者和消費者。針對我們前面示範的應用場景,即顯示在浏覽器中的某個Web頁面通過調用Web
API的方式來擷取它所需的資源,資源提供者為Web API本身,通過發送Ajax請求來調用Web
API的JavaScript程式為資源的消費者。
CORS旨在定義一種規範讓浏覽器在接收到從提供者擷取的資源時能夠正決定是否應該将此資源分發給消費者作進一步處理。CROS利用資源提供者的顯式授權來決定目标資源是否應該與消費者共享。換句話說,浏覽器需要得到提供者的授權之後才會将其提供的資源分發給消費者。那麼,資源的提供者如何進行資源的授權,并将授權的結果告訴浏覽器呢?
資源擷取請求被提供者接收之後,它可以根據該報頭确定提供的資源需要共享給誰。資源提供者的授權通過一個名為“Access-Control-Allow-Origin”的響應報頭來承載,其報頭值表示得到授權的站點。一般來說,如果資源的提供者認可了目前請求的“Origin”報頭攜帶的站點,那麼它會将該站點作為“Access-Control-Allow-Origin”響應報頭的值。
除了指定具體的源并對其作針對性授權之外,資源提供者還可以将“Access-Control-Allow-Origin”報頭值設定為“*”對所有消費者授權。換言之,如果作了這樣的設定,意味着由其提供的是一種公共資源,是以在做此設定之前需要慎重。如果針對請求着的授權不被允許,資源提供者可以将此響應報頭值設定為“null”,或者讓響應不具有此報頭。
當浏覽器接收到包含資源的響應之後,會提取此“Access-Control-Allow-Origin”響應報頭的值。如果此值為“*”或者包含的源清單包含此前請求的源(即請求的“Origin”報頭值),意味着資源的消費者擷取了提供者擷取和操作資源的權限,是以浏覽器會允許JavaScript程式操作擷取的資源。如果此響應報頭不存在或者其值為“null”,用戶端JavaScript程式針對資源的操作會被拒絕。
資源提供者除了通過設定“Access-Control-Allow-Origin”報頭對提供的主體資源進行授權之外,還可以通過設定另一個名為“Access-Control-Expose-Headers”的報頭對響應報頭進行授權。具體來說,此“Access-Control-Expose-Headers”的報頭用于設定一組直接暴露給用戶端JavaScript程式的響應報頭,沒有在此清單的響應報頭 對于用戶端JavaScript程式是不可見的。
但是由此實作的針對響應報頭的授權針對簡單響應報頭是無效的,用戶端JavaScript程式總是具有擷取它們的權限。對于CORS規範來說,這裡所謂的“簡單響應報頭(Simple Response Header)”包含如下6種。
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
我們知道用于實作Ajax請求的XMLHttpRequest具有一個getResponseHeader方法,調用它會傳回一組響應報頭的清單。按照這裡介紹的針對響應報頭的授權原則,隻有在“Access-Control-Expose-Headers”報頭中指定的報頭和簡單響應報頭才會包含在該方法傳回的清單中。
W3C的CORS規範将跨域資源請求劃分為兩種類型,一種被稱為“簡單請求(Simple Request)”。要弄清楚CORS規範将那些類型的跨域資源請求劃分為簡單請求的範疇,需要額外了解幾個名稱的含義,其中包括“簡單(HTTP)方法(Simple Method)”、“簡單(請求)報頭(Simple Header)”和“自定義請求報頭(Author Request Header/Custom Request Header)”。
CORS規範将GET、HEAD和POST這三個HTTP方法視為“簡單HTTP方法”,而将請求報頭Accept, Accept-Language, Content-Language以及采用如下三種媒體類型的報頭Content-Type稱為“簡單請求報頭”
application/x-www-form-urlencoded
multipart/form-data
text/plain
請求的報頭包含兩種類型,一類是通過浏覽器自動生成的報頭,另一種則是由JavaScript程式自行添加的報頭(比如調用XMLHttpRequest的setRequestHeader方法可以為生成的Ajax請求添加任意報頭),後者被稱為“自定義報頭”。
CORS規範将服務如下條件的跨域資源請求劃分為簡單請求:請求采用簡單HTTP方法,并且其自定義請求報頭空或者所有自定義請求報頭均為簡單請求報頭。之是以作如此劃分是因為具有這些特性的請求不是以更新(添加、修改和删除)資源為目的,服務端對請求的處理不會導緻自身維護資源的改變。
對于簡單跨域資源請求來說,浏覽器将兩個步驟(取得授權和擷取資源)合二為一,由于不涉及到資源的改變,是以不會帶來任何副作用(Side
Effect)。如果針對請求的處理過程會涉及到對資源的改變,這樣做就會有問題了。按照CORS規範的規定,浏覽器應該采用一種被稱為“預檢(Preflight)”的機制來完成非簡單跨域資源請求。
所謂預檢機制就是說浏覽器在發送真正的跨域資源請求前,先發送一個預檢請求(Preflight
Request)。預檢請求為一個采用HTTP-OPTIONS方法的請求,這是一個不包含主體的請求,同時使用者憑證相關的報頭也會被剔除。基于真正資源請求的一些輔助授權的資訊會包含在此預檢請求的相應報頭中。除了代表請求頁面所在站點的“Origin”報頭之外,如下所示的是兩個典型的請求報頭。
Access-Control-Request-Method:真正跨域資源請求采用的HTTP方法。
Access-Control-Request-Headers:真正跨域資源請求攜帶的自定義報頭清單。
資源的提供者在接收到預檢請求之後,根據其提供的相關報頭進行授權檢驗,具體的檢驗邏輯即包括确定請求站點是否值得信任,以及請求采用HTTP方法和自定義報頭是否被允許。如果預檢請求沒有通過授權檢驗,資源提供者一般會傳回一個狀态為“400,
Bad Reuqest”的響應。反之則會傳回一個狀态為“200,
OK”的響應,授權相關資訊會包含在響應報頭中。除了上面介紹的“Access-Control-Allow-Origin”和“Access-Control-Expose-Headers”報頭之外,預檢請求的響應還具有如下3個典型的報頭。
Access-Control-Allow-Methods:跨域資源請求允許采用的HTTP方法清單。
Access-Control-Allow-Headers:跨域資源請求允許攜帶的自定義報頭清單。
Access-Control-Max-Age:浏覽器可以将響應結果進行緩存的時間(機關為秒),這樣可以讓浏覽器避免頻繁地發送預檢請求。
浏覽器在接收到預檢響應之後,會根據響應報頭确定後續發送的真正跨域資源請求是否會被接受,相關的檢驗包括針對服務端允許站點以及HTTP方法和自定義請求報頭(利用響應報頭“Access-Control-Allow-Methods”和“Access-Control-Allow-Headers”)的檢驗。具體的檢驗邏輯如下
通過請求的“Origin”報頭表示的源站點必須存在于“Access-Control-Allow-Origin”響應報頭辨別的站點清單中。
響應報頭“Access-Control-Allow-Methods”不存在,或者預檢請求的“Access-Control-Request-Method”報頭表示的請求方法在其清單之内。
預檢請求的“Access-Control-Request-Headers”報頭存儲的報頭名稱均在響應報頭“Access-Control-Allow-Headers”表示的報頭清單之内。
隻有在确定服務端一定會接受的情況下,浏覽器才會發送真正跨域資源請求。預檢響應結果會被浏覽器緩存,在“Access-Control-Max-Age”報頭設定的時間内,緩存的結果将被浏覽器使用者進行授權檢驗,是以在此期間不會再有預檢請求發送。
在預設情況下,利用XMLHttpReuqest發送的Ajax請求不會攜帶使用者憑證相關的敏感資訊,這裡的使用者憑證類型包括Cookie、HTTP-Authentication報頭以及用戶端X.509證書(采用支援用戶端證書的TLS/SSL)等。如果需要使用者憑證附加到Ajax請求上,需要将XMLHttpReuqest的withCredentials
屬性設定為True。
對于CORS來說,是否支援使用者憑證也是授權檢驗的一個環節。換句話說,隻有在服務端顯式支援使用者憑證的情況下,攜帶了使用者憑證的請求才會被認為是有效的。在W3C的CORS規範來說,服務端利用響應報頭“Access-Control-Allow-Credentials”來表明自身是否支援使用者憑證。
也就是說,如果客戶用戶端JavaScript程式利用一個withCredentials屬性為true的XMLHttpReuqest發送了一個跨域資源請求,但是浏覽器得到的響應中不具有一個值為“true”的響應報頭“Access-Control-Allow-Credentials”,它對擷取資源的操作将會浏覽器拒絕。
上面我們對W3C的CORS規範作了概括性的介紹,由于篇幅所限,很多的細節并沒有涉及。如果讀者朋友們對此有興趣,我個人強烈推薦直接閱讀W3C的官方文檔。由于官方文檔的文字描述較為“生硬”,可能需要多讀幾遍才能将資源提供者和浏覽器如何處理資源授權流程搞清楚。
CORS系列文章
作者:蔣金楠
微信公衆賬号:大内老A
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識别二維碼)關注個人公衆号(原來公衆帳号蔣金楠的自媒體将會停用)。
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。
<a href="http://www.cnblogs.com/artech/p/cors-4-asp-net-web-api-02.html" target="_blank">原文連結</a>