什么是同源策略?
同源策略是瀏覽器的一項最為基本同時也是必須遵守的安全策略。同源策略的存在,限制了“源”自A的腳本只能操作“同源”頁面的DOM,“跨源”操作來源於B的頁面將會被拒絕。所謂的“同源”,必須要求相應的URI在如下3個方面均是相同的。
- 主機名稱(域名/子域名或者IP地址)
- 端口號
- 網絡協議(Scheme,分別采用“http”和“https”協議的兩個URI被視為不同源
對於一段JavaScript腳本來說,其“源”與它存儲的地址無關,而取決於腳本被加載的頁面。比如在某個頁面中通過<script>標簽引用了來源於不同地方的兩個JavaScript腳本,它們均與當前頁面同源。
<script src="http://www.a.com/scripts/a.js"></script>
<script src="http://www.b.me/scripts/b.js"></script>
除了<script>標簽其它一些具有src屬性的標簽(比如<img>),它們均具有跨域加載資源的能力,所以同源策略對它們不做限制。
同源策略主要限制了通過XMLHttpRequest實現的Ajax請求,如果請求的是一個“異源”地址,瀏覽器將不允許讀取返回的內容。
JSONP實現跨域資源共享
通過<script>標簽的src屬性加載的JavaScript腳本
<script type="text/javascript" src="http://localhost:8080/api/test?callback=test"></script> <script type="text/javascript"> function test(arg){ } </script>
JSONP是利用<script>的src標簽加載的腳本不受同源策略約束而采取的一種編程技巧,不是一種官方協議。由於具有src屬性的HTML標簽均通過HTTP-GET的方式來加載目標資源,JSONP只適用於HTTP-GET請求。
我們可以利用JQuery發送JSONP的Ajax跨域請求,調用$.ajax方法並將dataType參數設置為“jsonp”
<script type="text/javascript"> $(function () { $.ajax({ dataType : "jsonp" }); }); </script>
CORS(Cross-Origin Resource Sharing)
基於Web的資源共享涉及到兩個基本的角色,即資源的提供者和消費者。即顯示在瀏覽器中的某個Web頁面通過調用Web API的方式來獲取它所需的資源,資源提供者為Web API本身,通過發送Ajax請求來調用Web API的JavaScript程序為資源的消費者。
CORS旨在定義一種規范讓瀏覽器在接收到從提供者獲取的資源時能夠正決定是否應該將此資源分發給消費者作進一步處理。CROS利用資源提供者的顯式授權來決定目標資源是否應該與消費者共享。瀏覽器需要得到提供者的授權之后才會將其提供的資源分發給消費者。
如果瀏覽器 自身提供對CROS的支持,由它發送的請求會攜帶一個名為“Origin”的報頭表明請求頁面所在的站點。
資源獲取請求被提供者接收之后,它可以根據該報頭確定提供的資源需要共享給誰。資源提供者的授權通過一個名為“Access-Control-Allow-Origin”的響應報頭來承載,其報頭值表示得到授權的站點。一般來說,如果資源的提供者認可了當前請求的“Origin”報頭攜帶的站點,那么它會將該站點作為“Access-Control-Allow-Origin”響應報頭的值。
當瀏覽器接收到包含資源的響應之后,會提取此“Access-Control-Allow-Origin”響應報頭的值。如果此值為“*”或者包含的源列表包含此前請求的源(即請求的“Origin”報頭值),意味着資源的消費者獲取了提供者獲取和操作資源的權限,所以瀏覽器會允許JavaScript程序操作獲取的資源。如果此響應報頭不存在或者其值為“null”,客戶端JavaScript程序針對資源的操作會被拒絕。
簡單(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方法,並且其自定義請求報頭空或者所有自定義請求報頭均為簡單請求報頭。
對於簡單跨域資源請求來說,瀏覽器將兩個步驟(取得授權和獲取資源)合二為一。如果針對請求的處理過程會涉及到對資源的改變,這樣做就會有問題了。按照CORS規范的規定,瀏覽器應該采用一種被稱為“預檢”的機制來完成非簡單跨域資源請求。
所謂預檢機制就是說瀏覽器在發送真正的跨域資源請求前,先發送一個預檢請求。預檢請求為一個采用HTTP-OPTIONS方法的請求,這是一個不包含主體的請求,同時用戶憑證相關的報頭也會被剔除。基於真正資源請求的一些輔助授權的信息會包含在此預檢請求的相應報頭中。除了代表請求頁面所在站點的“Origin”報頭之外,如下所示的是兩個典型的請求報頭。
- Access-Control-Request-Method:真正跨域資源請求采用的HTTP方法。
- Access-Control-Request-Headers:真正跨域資源請求攜帶的自定義報頭列表。
資源的提供者在接收到預檢請求之后,根據其提供的相關報頭進行授權檢驗,具體的檢驗邏輯即包括確定請求站點是否值得信任,以及請求采用HTTP方法和自定義報頭是否被允許。如果預檢請求沒有通過授權檢驗,資源提供者一般會返回一個狀態為“400, Bad Reuqest”的響應。反之則會返回一個狀態為“200, OK”的響應,授權相關信息會包含在響應報頭中。除了上面介紹的“Access-Control-Allow-Origin”報頭之外,預檢請求的響應還具有如下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。
W3C的CORS規范,服務端利用響應報頭“Access-Control-Allow-Credentials”來表明自身是否支持用戶憑證。
JavaScript程序利用一個withCredentials屬性為true的XMLHttpReuqest發送了一個跨域資源請求,但是瀏覽器得到的響應中不具有一個值為“true”的響應報頭“Access-Control-Allow-Credentials”,它對獲取資源的操作將會瀏覽器拒絕。
ps:本文參考摘要《ASP.NET Web API 2框架揭秘》一書