本文為譯文。
簡介
APIS是可以將富網頁應用串連在一起的線程。但是這個應用難以轉給瀏覽器,跨域請求技術的選擇被限制了,類似JSONP(由於安全考慮,使用會被限制),或者配置代理(設置和維護都比較頭痛)。
Cross-Origin Resource Sharing(CORS)是允許來自瀏覽器的跨域通信的W3C規范。通過設置XMLHttpRequest的頭部,CORS允許開發者使用類似同域中請求慣用的方法。
CORS的用法很簡單,假設網站alice.com有一些bob.com想要獲取的數據。這類型的請求以前通常在瀏覽器的同源策略下不被允許。但是,借助CORS請求,alice.com可以加一些特殊回應頭信息允許bob.com取得數據。
可以從這個例子里面看出來,支持CORS需要服務器和客戶端之間的協調。幸運的是,如果你是客戶端開發者,你可以屏蔽掉這中間大多數的細節。這篇文章余下的部分會展示客戶端如何進行跨域請求,及服務器如何配置來支持CORS。
發起CORS請求
這部分介紹如何使用javascript發起CORS請求。
創建XMLHttpRequest請求:
下列瀏覽器支持CORS:
Chrome 3+
Firefox 3.5+
Opera 12+
Safari 4+
Internet Explorer 8+
Chrome,Firefox,Opera,Safari都使用XMLHttpRequest2對象,IE使用類似的XDomainRequest對象,與他本身對應的XMLHttpRequest對象工作機制基本相同,但是另外增加了安全防范。
首先要創建合適的請求對象,像這樣:
事件處理
原始的XMLHttpRequest只有一個事件處理onreadystatechange,來處理所有的響應。雖然onreadystatechange依然可以使用,XMLHttpRequest2引進了一系列新的處理,下面是一個列表:
在大多數情況下,你只僅僅想處理onload和onerror事件:
當有錯誤的時候,瀏覽器不會報告是什么錯誤。例如,FF對所有錯誤報告一個狀態0和空statusText。瀏覽器也會向控制輸出台報錯,但是這個消息沒法被javascript獲取到。當處理onerror的時候,會知道有一個錯誤發生了,但沒有更多信息了。
withCredentials
標准的CORS在默認情況下不發送和設置任何cookie。為了使cookie成為請求的一部分包含進來,需要設置XMLHttpRequest的withCredentials屬性為true:
為了達成這個目的,服務器也需要設置Access-Control-Allow-Credientials為true來允許認證。
withCredentials屬性會包含來自遠程域的請求的任何cookie。注意這些cookies仍然遵循同源策略,所以你的javascript代碼仍然從document.cookie中訪問不到這些cookies或者請求頭.他們只能被遠程域所控制。
發送請求
現在你的CORS請求已經配置好了,你准備發送請求。這個通過調用send()方法來實現:
如果請求包含請求體,可以被指定為一個參數給send().
就是醬紫!假定服務器已經完全配置好來響應CORS請求了,你的onload處理會收到響應,就像你十分熟悉的標准的同域XHR那樣。
最后最后一個例子
這是一個完整的CORS請求例子。運行下例子在瀏覽器debugger里面看看發送的請求。
在服務器端添加CORS支持
多數的CORS重擔在瀏覽器和服務器之間被處理了。瀏覽器增加了額外的一些頭,有時發起額外的請求,在CORS請求期間。這些額外的請求對於客戶端是不可見的(可以使用Wireshark等工具來捕獲)。
瀏覽器廠商對於瀏覽器端的實現是有責任的。下面了解下服務器端如何配置來支持CORS。
請求的類型
跨域請求分類:
1.簡單請求
2.非簡單請求
簡單請求不需要使用CORS從瀏覽器發出請求。例如,一個JSONP跨域請求可以是一個跨域的GET請求。
非簡單請求,在瀏覽器與服務器間需要一點額外的交互。
處理一個簡單請求
以一個客戶端的簡單請求為例。下面代碼展示了javascript的一個GET請求,以及瀏覽器發送的實際請求。CORS中特殊的頭粗體顯示。
首先需要注意的是,一個合法的CORS請求總是包含Origin頭。這個Origin頭是瀏覽器加上去的,並且不能被用戶控制。這個頭的值是來自請求源的協議(例如http),域(例如bob.com),和端口(只在不是默認端口的情況下包含,例如81);例如http://api.alice.com.
Origin頭的存在不意味着請求是一個跨域請求。雖然所有跨域的請求都會包含Origin頭,一些同域請求同樣也會包含Origin頭。例如,火狐在同域請求中不包含Origin頭。但是Chrome和Safari在同域的POST/PUT/DELETE請求(同域的GET請求不會有Origin頭)中會包含Origin頭。下面有一個同域請求包含Origin頭的例子:
值得慶幸的是瀏覽器在同域請求時不會期望CORS請求頭。同域請求的響應發送給用戶,不管是否有CORS請求頭。然而,如果服務器代碼返回了如果Origin不匹配允許的域列表的錯誤,確保包含請求的來源域。
所有CORS相關的的頭都是Access-Control為前綴的。下面是每個頭的一些細節。
Access-Control-Allow-Origin(必須)-這個頭在所有合法的CORS響應中必須被包含;發送頭會引起CORS請求失敗。這個頭的值可以是Origin請求頭的回應(就像上面的例子里面一樣),或者是允許來自任何域請求的"*"。如果你希望任何站點都可以獲取數據,使用"*"就很好。但是如果你希望更好的控制誰可以獲取你的數據,在頭中使用一個實際的值。
Access-Control-Allow-Credentials(可選)-默認情況下,cookies在CORS請求中不被包含。使用這個頭意味着在CORS請求中應當包含cookies.這個頭的唯一合法值是true(全部小寫)。如果不需要cookies,不要包含這個頭(而不是把值設置為false).
Access-Control-Allow-Credentials頭與XMLHttpRequest2的withCredentials屬性結合使用。所有這些屬性都必須設置為true以便於CORS請求能夠成功。如果withCredentials是true,但是沒有Access-Control-Allow-Credentials頭,請求會失敗(反之亦然)。
上面提到過,除非你確定需要cookies包含在請求中,否則不設置這個頭。
Access-Control-Expose-Headers(可選)-XMLHttpRequest2對象有一個返回特別響應頭值的getResponseHeader()方法。在CORS請求中,getResponseHeader()只能獲取簡單的響應頭。簡單響應頭定義如下:
如果希望客戶端獲取其他頭,必須用Access-Control-Expose-Headers頭。這個頭的值是一個逗號分隔的想要公開給客戶端的響應頭列表。
處理非簡單請求
上面就是如何處理簡單GET請求,但如果想要做更多的事情要怎么做?也許你想要支持其他HTTP動作,像PUT或DELETE,或者想要使用Content-Type:application/json支持JSON.你就需要處理所謂非簡單請求。
非簡單請求看上去像到客戶端的一個單一請求,但是實際上在遮罩下包含了兩個請求。瀏覽器首先發出一個預先請求,就好像詢問服務器應允進行實際請求。一旦被允許,瀏覽器發送實際請求。瀏覽器透明的處理這兩個請求的細節。預響應也可以被緩存下來以便不需要在每個請求前都發送。
下面是一個非簡單請求的例子:
像簡單請求一樣,瀏覽器給每個請求加上Origin頭,包含預請求。預請求像HTTP OPTIONS請求一樣發出(所以確保服務器可以響應這個方法).他也包含額外的一些頭:
Access-Control-Request-Method-HTTP實際請求的方法。這個請求頭總是被包含,即使HTTP請求是一個簡單請求,就像早些定義的那些(GET,POST,HEAD).
Access-Control-Request-Headers-一個在請求中包含的非簡單頭的逗號分隔的列表。
預請求是在發送實際請求前,問詢服務器是否允許實際請求的方式。服務器應當檢查上面的兩個頭來確認HTTP方法和請求頭是合法的及可以接受的。
如果HTTP請求方法和頭是合法的,服務器會如下響應:
Access-Control-Allow-Origin(必須)-如簡單響應一樣,預響應必須包含這個頭。
Access-Control-Allow-Method(必須)-所支持的HTTP方法逗號分隔的列表。注意雖然預請求只是詢問HTTP方法是否允許,響應頭可以包含所有支持的HTTP方法。這個是有幫助的,因為預響應可能會被緩存,所以單一的預響應可以包含多種請求類型的細節。
Access-Control-Allow-Headers(如果請求頭包含Access-Control-Allow-Headers就必須包含)-逗號分隔的所支持的請求頭列表。像上面的Access-Control-Allow-Methods一樣,這個可以列出服務器支持的所有頭(不僅僅是在預請求中請求的頭)。
Access-Control-Allow-Credentials(可選)-同簡單請求。
Access-Control-Max-Age(可選)-在每個請求上進行預請求是代價很高的,因為瀏覽器對於客戶端的請求會進行兩次請求。這個頭的值允許預響應緩存一個指定的秒數。
一旦預請求給到了應允,瀏覽器發送實際請求。實際請求看起來像簡單請求,響應以同樣的方式處理:
如果服務器想否決CORS請求,可以只返回一個通用的響應(像HTTP 200),不帶任何CORS請求頭。如果預請求時的HTTP方法或者頭不合法,服務器可能想要否決請求。由於在響應中沒有CORS特定的頭,瀏覽器斷定請求是不合法的,不發送實際請求:
如果在CORS請求中有錯誤,瀏覽器會觸發客戶端的onerror事件處理。也會在控制台上打印下面的錯誤:
瀏覽器不會給為什么錯誤會發生的細節信息,只告訴你出了些問題。
關於安全
雖然CORS奠定了跨域請求的基礎工作,CORS頭不是健全的安全實踐的替代。在站點上不能夠依賴CORS頭保護資源安全。使用CORS頭給予瀏覽器跨域請求的接入,但是使用其他的安全手段,像cookies或者OAuth2,如果需要額外的安全限制的話。
來自jQuery的CORS
jQuery的$.ajax()方法可以用來進行常規XHR請求和CORS請求。一點點jQuery相關實現的筆記:
- jQuery的CORS實現不包含IE的XDomainRequest對象。但是有jQuery插件可以用這個。可以在http://bugs.jquery.com/ticket/8283上查看細節。
- 如果瀏覽器支持CORS的話(在IE中會返回false,看下上條說明),$.support.cors布爾量會被設置成true。這是檢查是否支持CORS的快速方法。
下面是一個用jQuery進行CORS請求的例子。注釋給出了確切的屬性如何與CORS交互。
Chrome擴展件的跨域
Chrome擴展件以兩種不同的方式支持跨域:
- 在manifest.json中包含域-Chrome擴展件可以向任何域發送跨域請求,如果域被包含在manifest.json文件的permissions部分:
服務器不需要包含任何額外的CORS頭或者做另外的工作來使請求成功。
- CORS請求-如果域不在manifest.json文件中,Chrome擴展件進行一個標准的CORS請求。Origin頭的值是"chrome-extension://[CHROME EXTENSION ID]".這意味着來自Chrome擴展件的請求受本文提到的同樣的CORS請求規則的約束。
CORS w/圖片
在Canvas和WebGL上下文中,跨域圖片可以造成很大的問題。可以在img元素上使用crossOrigin屬性。
CORS服務器流程圖
下面的流程圖說明了服務器如何確定在CORS響應中需要加哪些頭。
--end--