同源政策
同源政策是浏覽器保護使用者安全上網的重要措施,協定、域名、端口号三者相同即為同源。
不同源下,浏覽器不允許js操作Cookie、LocalStorage、DOM等資料或頁面元素,也不允許發送ajax請求,同源下則不受影響。
下圖是在Chrom控制台中發送ajax跨域請求的報錯資訊:
圖檔中黃色部分提示響應被阻止,說明在跨域的情況下,請求依然發送到了伺服器且伺服器傳回了資料,隻是被浏覽器攔下了。
對于跨域問題可以使用CORS來解決,使用CORS時,HTTP請求分為兩種情況:簡單請求與複雜請求。
簡單請求
滿足以下三點即為簡單請求:
- HTTP請求方法為GET、POST或HEAD
- HTTP請求頭隻能包含
或Accept, Accept-Language, Content-Language, Content-Type
Last-Event-ID
- ContentType的值隻能為以下三種:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
複雜請求
除簡單請求之外即為複雜請求。浏覽器在發送複雜請求前會先發送Preflight request(預檢請求),即發送OPTIONS請求。注意是浏覽器發送的,使用者無感。
預檢請求頭包含兩個特定字段:
-
Access-Control-Request-Method
表示後續請求會用到的HTTP方法,該字段必選
-
Access-Control-Request-Headers
後續請求中所設定的請求頭部資訊,注意,這裡不包含浏覽器預設設定的頭部字段,如:
。該字段可不發送。User-Agent
伺服器會檢查對預檢請求中的
Origin
、
Access-Control-Request-Method
Access-Control-Request-Headers
字段值,并傳回正常的HTTP響應。
浏覽器根據傳回資訊判斷後續請求是否符合伺服器端跨域要求,不符合則抛出錯誤資訊。通過預檢請求後,則發送後續請求,此時和簡單請求無差别。
伺服器配置CORS的幾個字段
-
Access-Control-Allow-Origin
必選,設定允許哪些源通路伺服器資源
-
Access-Control-Allow-Methods
必選,設定允許哪些HTTP方法
- 設定HTTP請求頭中包含哪些字段,如果浏覽器請求包括
字段,則必選Access-Control-Request-Headers
以上三個字段為常用字段,其餘字段配置參考:CORS policy options。
withCredentials與Cookie的跨域問題
Cookie受到同源政策的限制沒有那麼嚴格,預設情況下,隻要發送請求方所在域與Cookie的Domain值相同即可将cookie發送至伺服器端,無需考慮協定和端口号。在預設情況下,用戶端發起的HTTP請求會帶上目标域的Cookie,但無法攜帶其它屬于其它的域Cookie。
我們可以借助XMLHttpRequest
對象的withCredentials屬性及CORS的Access-Control-Allow-Credentials二者來實作跨域的Cookie發送和寫入。
var xhr=new XMLHttpRequest();
xhr.open('GET','http://www.target.com:8093/api/GetAllProductType');
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200){
console.log(xhr.response);
}
}
// 這裡使用withCredentials屬性來發送Cookie
xhr.withCredentials=true;
xhr.send();
注意,在使用
withCredentials
時,伺服器端不能将
Access-Control-Allow-Origin
的值配置為*,否則用戶端會報錯:
Access to XMLHttpRequest at ''http://www.target.com:8093/api/GetAllProductType' from origin 'http://www.request.com:8094' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
一個問題
上周在ASP.NET Web API 2中使用CORS,報錯:
The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed
。原因是伺服器端配置了兩次CORS,導緻傳回了兩個
Access-Control-Allow-Origin:*
但浏覽器隻允許一個。
經過排查發現在
Web.config
檔案中也配置了CORS,與代碼中的配置重複,注釋掉之後問題解決。該問題參考了:stackoverflow上的回答。
小結
同源政策是浏覽器為保障使用者(資料)安全而對JS功能進行一定限制。畢竟HTML與CSS隻負責網頁結構與樣式,不具備操作頁面元素及與伺服器互動的功能。
離開浏覽器環境後跨域問題也就不複存在。
嚴格的限制會導緻一些不便,故同源政策開了幾個口子:
-
Cookie共享
子域名可以共享父級域名的cookie
- 嵌入式資源擷取
等标簽擷取資源不受同源政策限制,這也是JSONP實作跨域的原理<script>,<img>,<link>
常用處理跨域請求的方式有JSONP和CORS:
-
JSONP
需要前後端協作處理且隻支援GET請求
不是标準規範
對老式浏覽器友好(這裡想到了老古董IE:)
-
CORS
支援GET、POST、PUT、DELETE等多種請求
伺服器端配置簡單且不需要前端寫額外的代碼
目前主流浏覽器均支援CORS規範
推薦閱讀
浏覽器家族的安全反擊戰
Enable Cross-Origin Requests (CORS) in ASP.NET Core
前後端分離 | 關于登入狀态那些事
Cross-Origin Resource Sharing (CORS)
Cookie中的幾個概念
當 CORS 遇到 SameSite
跨站(cross-site)、跨域(cross-origin)、SameSite與XMLHttpRequest.withCredentials