同源策略和資源跨域共用 1、同源策略 同源策略,它是由Netscape提出的一個著名的安全策略。現在所有支持JavaScript 的瀏覽器都會使用這個策略。所謂同源是指,功能變數名稱,協議,埠相同。 1.1、目的 主要是為了保證用戶信息的安全,防止網站竊取用戶數據。假如沒有同源策略,可能就會有下麵這種情況 ...
同源策略和資源跨域共用
1、同源策略
同源策略,它是由Netscape提出的一個著名的安全策略。現在所有支持JavaScript 的瀏覽器都會使用這個策略。所謂同源是指,功能變數名稱,協議,埠相同。
1.1、目的
主要是為了保證用戶信息的安全,防止網站竊取用戶數據。假如沒有同源策略,可能就會有下麵這種情況的發生。用戶訪問兩個網站A/B,並登錄了A網站,A網站會在電腦本地存儲Cookie或者Token等等,在訪問B網站的時候,B網站就可以訪問這些本地的存儲信息,B網站可以使用用戶的Cookie去登錄A網站,那這樣用戶信息就被泄露了。
1.2、限制範圍
- Cookie、LocalStorage和indexDB無法訪問(只有同源的網頁才能共用Cookie)
- DOM無法獲得(父視窗和子視窗的地址是同源的才能獲取子視窗的信息)
- AJAX請求不能被髮送(AJAX請求只能發送給同源的網址)
要知道一點,這些限制其實都是瀏覽器做的限制。
2、跨域資源共用
跨域資源共用跟同源策略相反。在整個跨域通信過程中,瀏覽器會自動識別此次請求是否跨域,一旦發現跨域,就自動添加請求頭信息(如Origin)或者自動發送一次請求方式為option的預請求。瀏覽器將CORS請求分為兩類:簡單請求和非簡單請求。
2.1、簡單請求
當瀏覽器的請求方式是Head、Get或者Post,並且HTTP的頭信息中不會超出以下欄位:
-
Accept
-
Accept-Language
-
Content-Language
-
Origin
時,瀏覽器會將該請求定義為簡單請求,否則就是非簡單請求。當瀏覽器判斷為簡單請求後,瀏覽器會自動再請求報文頭中加上Origin欄位,表明此次請求來自的地址(協議+功能變數名稱+埠)。然後伺服器需要去判斷是否接受這個來源的請求。如果允許伺服器端返回的頭部中需要有Access-Control-Allow-Origin,其值為請求時Origin欄位的值或*(表示接受任意源的請求)。請求頭中還會有Access-Control-Allow-Methods表示伺服器允許的跨域請求的方式。Access-Control-Allow-Headers表示請求頭中允許出現的欄位。
2.2、 非簡單請求
當瀏覽器判斷為非簡單請求後,會發送兩次請求,首先瀏覽器會自動發送一個請求方式為options的請求,併在請求頭中
- 加上Access-Control-Request-Method表示下次請求的方法,
- 加上Origin表明來源,
- 加上Access-Control-Request-Headers表示下次請求的請求頭中額外的欄位。
伺服器收到請求後,需要獲取這三個請求頭中的值,併進行判斷,確認是否允許進行跨域。如果伺服器返回的請求頭中沒有任何CORS相關的請求頭信息,瀏覽器會認為不通過預檢,也不會進行第二次請求。
伺服器如果接受跨域並驗證通過了options的請求,會返回Access-Control-Allow-Origin(表明允許跨域請求的源)、Access-Control-Allow-Methods(允許跨域請求的請求方式)、Access-Control-Allow-Headers(允許請求頭中包含的額外欄位)。然後瀏覽器才會發送真正的請求。
(第一次options請求)
(第二次請求)
二、服務端實現CORS
在.Net Core Web Api中使用很簡單,首先安裝包Microsoft.AspNet.WebApi.Cors,在StartUp中添加下麵兩句
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //添加Cors,並配置CorsPolicy services.AddCors(options => options.AddPolicy("CorsTest", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod())); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
//註意UseCors()要在UseMvc()之前 app.UseCors("CorsTest"); app.UseMvc(); }
在使用的時候只需要在Controller或者Action中加上特性[EnableCors("CorsTest")]
[EnableCors("CorsTest")] public class ValuesController : Controller { private ILogger<ValuesController> _logger; public ValuesController(ILogger<ValuesController> logger) { _logger = logger; } [HttpGet] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } }
現在服務端已經配置好了,現在需要通過前端跨域請求
<html> <head> 測試 </head> <body> 測試 </body> </html> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $(function () { $.ajax({ type: "get", url: "http://localhost:7000/api/values", beforeSend: function (request) {//在請求報文頭中加入Authorization 目的是讓請求為非簡單請求 request.setRequestHeader("Authorization", "Bearer 071899A00D4D4C5B1C41A6B0211B9399"); }, success: function (result) { alert(result); } }, "json"); }); </script>
測試結果如下圖:
(options請求)
(第二次請求)
上面配置允許所有的地址請求這個介面,也可以單獨配置某個地址。
services.AddCors(options => options.AddPolicy("CorsTest", p => p.WithOrigins("http://localhost:8089")
.AllowAnyHeader()
.AllowAnyMethod()));
三、解析Cors源碼
打開CORS源碼,主要的是CorsMiddleware、CorsOptions、CorsPolicy、CorsPolicyBuilder、CorsResult、CorsService這幾個類。
- CorsPolicy:就是我們在Startup中的配置,如允許哪些功能變數名稱可以跨域請求,允許哪些跨域請求方式,允許哪些額外的請求頭,每個配置對應一個名稱。
services.AddCors(options => options.AddPolicy("CorsTest", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
- CorsOptions:中包含一個字典IDictionary<string, CorsPolicy> PolicyMap,一個項目可能有過個Cors配置,所以這個CorsOptions就是通過配置名稱管理這些配置的。
- CorsPolicyBuilder:通過它來構造CorsPolicy。
- CorsResult:是驗證跨域過程得到的結果。如在第一次Options請求時,客戶端發送了Origi:http://localhost:8089,伺服器會返回Access-Control-Allow-Origin:http://localhost:8089,伺服器驗證http://localhost:8089這個功能變數名稱是否允許跨域,如果允許就將“http://localhost:8089”這個值存儲到CorsResult的AllowedHeaders中,在請求(第一次請求)返回的時候將這些加到HTTP請求頭中。
- CorsMiddleware:Cors中間件類,主要方法就是Invoke,每次HTTP請求都會調用這個方法。
public async Task Invoke(HttpContext context) {//判斷HTTP請求頭是否有Origin,由此判斷是不是跨域請求 if (context.Request.Headers.ContainsKey(CorsConstants.Origin)) { var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName); if (corsPolicy != null) { var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod];
//如果是跨域請求 判斷是不是第一次Options請求 if (string.Equals(context.Request.Method,CorsConstants.PreflightHttpMethod,StringComparison.OrdinalIgnoreCase) &&!StringValues.IsNullOrEmpty(accessControlRequestMethod)) {
//判斷是否允許當前請求跨域,根據HttpContext的內容和Cors配置 得到CorsResult,然後將CorsResult的內容添加到請求頭中(看下麵詳細解釋) ApplyCorsHeaders(context, corsPolicy); context.Response.StatusCode = StatusCodes.Status204NoContent; return; } else {// 執行第二次非Options請求 context.Response.OnStarting(state => { var (httpContext, policy) = (Tuple<HttpContext, CorsPolicy>)state; try { ApplyCorsHeaders(httpContext, policy); } catch (Exception exception) { _logger.FailedToSetCorsHeaders(exception); } return Task.CompletedTask; }, Tuple.Create(context, corsPolicy)); } } } await _next(context); } private void ApplyCorsHeaders(HttpContext context, CorsPolicy corsPolicy) { //通過HTTP上下文請求的數據和Cors配置 得到CorsResult
如在第一次Options請求時,客戶端發送了Origi:http://localhost:8089,Access-Control-Resquest-Methods:GET
伺服器會返回Access-Control-Allow-Origin:http://localhost:8089,Access-Control-Allow-Methods:GET
伺服器驗證http://localhost:8089這個功能變數名稱以GET請求方式是否允許跨域,
如果允許就將“http://localhost:8089”這個值存儲到CorsResult的AllowedHeaders中
將"GET"存儲到CorsResult的AllowedMethods中 var corsResult = _corsService.EvaluatePolicy(context, corsPolicy);
//將CorsResult中的值添加到相應頭中的,返回到客戶端 _corsService.ApplyResult(corsResult, context.Response); }
相對來說Cors源碼還是比較簡單的,很容易看懂。可以自己寫一個項目,然後掛上源碼單步調試。