ASP.NET Core MVC中的跨域Cors詳解
在一些專案中,我們經常會用到跨域,在jquery ajax時代,還採用的jsonp的方式進行跨域請求。但這種方式的安全性很低,api端對所有請求均開放了。
如今,NET CORE發展也是越來越完善了,對於跨域,也有完善的策略。
今天我們就來詳細講講 跨域 到底是怎麼一回事兒。
以下示例均以 JQuery 的ajax 為例。其他xhr 方式的非同步請求,雷同。
瀏覽器安全性可防止網頁向不處理網頁的域傳送請求。 此限制稱為同域策略。 同域策略可防止惡意站點從另一站點讀取敏感資料。
瀏覽器預設採用的是同域策略。但有時,我們可能希望允許其他網站向自己的應用發出跨源請求。
我們瀏覽器請求的方法有:PUT,POST,GET,DELETE,OPTIONS,HEAD,PATCH 這幾個。
實際上我們跨域請求某個API 時,瀏覽器是發起了2個請求,一個是OPTIONS 方法,一個是實際的資料請求方法(如GET/POST/PUT等)。
OPTIONS 請求,就像是探子一樣,先去問一下,是否允許跨域請求,若允許,OK,再實際傳送請求資料。 如下請求示例的截圖;
只有OPTIONS狀態正常,我們的POST請求才能正確獲取到API返回的資料。否則,將報 CORS 跨域異常。
知道了原理之後,我們先看一個簡單的中介軟體的處理方式。既然是給OPTIONS 請求正確的響應就行,那我們自定義一箇中間件來模擬。
跨域還跟幾個請求頭的配置有關係,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Credentials,即我們可以針對這幾個請求頭進行跨域策略的配置。下面會詳細講解。
我們來看這個中介軟體怎麼寫
public class CorsMiddleware { private readonly RequestDelegate next; public CorsMiddleware(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context) { if (context.Request.Headers.ContainsKey(CorsConstants.Origin)) {//定義4個請求頭相關配置 context.Response.Headers.Add("Access-Control-Allow-Origin", context.Request.Headers["Origin"]); //必須配置,此處寫法作用等於*,可以將context.Request.Headers["Origin"]直接用"*"代替,表示允許所有的請求。或者可配置只允許的域名,多個用英文逗號隔開 如 https://www.1633.com,http:1633.com //context.Response.Headers.Add("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD,PATCH"); //可以不配置 //context.Response.Headers.Add("Access-Control-Allow-Headers", context.Request.Headers["Access-Control-Request-Headers"]); //可以不配置 //context.Response.Headers.Add("Access-Control-Allow-Credentials", "true"); //可以不配置 if (context.Request.Method.Equals("OPTIONS")) { context.Response.StatusCode = StatusCodes.Status200OK; return; } } await next(context); } }
然後使用Startup.cs Configure 方法中呼叫中介軟體
app.UseMiddleware<CorsMiddleware>();
這個中介軟體,主要就2個配置
1:配置允許的來源頭,Access-Control-Allow-Origin ,若配置為* ,表示 允許任意來源的請求。
2:當請求方法是OPTIONS 時,直接進行 200 正常響應。
驗證方式也簡單,上面我們提到了以往我們ajax請求跨域,需要用jsonp,這次我們服務端已經加了允許跨域了,就直接用json請求發起。
服務端介面,我們在 Home控制器下定義了Test3 方法,返回一個常規的json格式的內容。
public JsonResult Test3() { var data = new { code = 3 }; return Json(data); }
把專案執行起來。然後我們在自己本機桌面上,建立一個 test.html文件,引入jquery
<!DOCTYPE html> <html> <head> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script> </head> <body> <input id="test3" type="button" value="測試3"/> </body> <script> $("#test3").bind("click",function(){ $.ajax({ url:"http://localhost:52842/home/test3", type:"get", dataType:"json", error:function(){alert("請求失敗");}, success:function(json){ alert(json.code); } }) }); </script> </html>
直接開啟這個html文件,點選按鈕
說明,我們跨域請求成功了。
假如我們沒呼叫這個跨域中介軟體,則會出現下圖結果:
OK,上面是以自定義中介軟體的方式來開篇的,下面才是我們重點要介紹的內容。。。
如何使用NETCore自帶的Cors中介軟體來設定跨域策略???
以下示例是基於.NET6.0的環境,低版本在startup.cs中寫同樣的程式碼即可。
主要是呼叫服務的AddCors進行跨域的配置
services.AddCors(options => { options.AddPolicy("MyPolicy", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); //MyPolicy 為自定義的策略名稱,與使用時相同即可。可以同時定義多個不同策略名稱的跨域策略 });
然後呼叫中介軟體執行
app.UseCors("MyPolicy"); //指定呼叫MyPolicy 配置的策略,表示全域性都採用該策略
這樣,我們就實現了一個很簡單的,全域性的跨域策略。
專案執行起來,可以正常跨域。
但,如果你覺得這樣就可以直接用到專案裡面去,那安全性就太低了。
上面的示例中,我們允許了所有的來源。這明顯是不安全的。而且知道的跨域配置項,還有不少。
下面,我們就詳細講下各個配置項,也可參考微軟官方文件:https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?source=recommendations&view=aspnetcore-7.0
AllowAnyOrigin:允許具有任何方案(http
或 https
)的所有源的 CORS 請求。 AllowAnyOrigin
不安全,因為 任何網站 都可以嚮應用發出跨域請求。
AllowAnyMethod:允許所有的請求方法。
AllowAnyHeader:允許所有請求頭。
AllowCredentials:允許有憑據,預設是無,匿名都可以。它不能跟AllowAnyOrigin共存。若是要允許所有的來源,還配置該項,那需要把 .AllowAnyOrigin() 替換成.SetIsOriginAllowed(_=>true)
以上4個均是允許所有的請求,不做任何限制。在實際專案中,AllowAnyOrigin,我們是不建議允許所有來源。
那實際專案中,我們應該如何配置,才能既實現跨域,又具有一定的安全性呢?我們採用以下幾個屬性來分別替代
WithOrigins:可替代AllowAnyOrigin方法,可以設定只允許跨域請求的來源域名,域名包含 http部分,最後不能以 /結尾,允許配置多個。
這樣,非我們配置的域名,就不能跨域來訪問我們的資源。
WithMethods:配置允許請求的方法。比如 .WithMethods("POST","GET"),表示只允許POST和GET的請求,其他請求不允許。
WithHeaders:表示請求的Access-Control-Allow-Headers頭必須是與WithHeaders配置的相等,必須是相等才行,包含也不行。一般情況下,我們可以不配置這個。而是採用AllowAnyHeader。
這樣,配置如下
services.AddCors(options => { options.AddPolicy("MyPolicy", p => p.WithOrigins("null", "http://localhost:5151", "https://www.baidu.com") .WithMethods("POST","GET") .WithHeaders("kywtoken") ); });
若實際專案中僅有 來源需要配置,那可以這樣
services.AddCors(options => { options.AddPolicy("MyPolicy", p => p.WithOrigins("null", "http://localhost:5151", "https://www.baidu.com") ); });
若我們的場景是允許同一個主域下的所有子域名都可以跨域請求,則配置的來源可以設定為萬用字元。同時 增加 SetIsOriginAllowedToAllowWildcardSubdomains()方法,如下:
services.AddCors(options => { options.AddPolicy("MyPolicy", p => p.WithOrigins("https://*.1633.com").SetIsOriginAllowedToAllowWildcardSubdomains() ); });
這樣,整個.1633.com的子域名都生效。
還有一個方法,SetPreflightMaxAge
這個方法的意思是可以設定OPTIONS的快取時間,啥意思呢?
上面我們講到了,每次發起一個跨域請求時,其實瀏覽器都會轉成2個請求。 1個是OPTIONS 請求,一個是實際的請求。那對伺服器來說,就是2次請求了。
為了降低伺服器的負載,我們可以通過 配置SetPreflightMaxAge 來告知瀏覽器,OPTIONS請求多長時間有效,不用每次都發個OPTIONS來諮詢了。直接發請求過來就可以給你響應了。
如下:
services.AddCors(options => { options.AddPolicy("MyPolicy", p => p.WithOrigins("https://*.1633.com").SetIsOriginAllowedToAllowWildcardSubdomains().SetPreflightMaxAge(TimeSpan.FromHours(1)) ); });
我們配置了快取1小時,也就是1小時內,同個域名的請求,不會重新發起OPTIONS 請求的。就減少了請求的次數。
所以綜上,我們一般可以這麼配置:
services.AddCors(options => { options.AddPolicy("MyPolicy", p => p.WithOrigins("https://*.1633.com").SetPreflightMaxAge(TimeSpan.FromHours(1)).AllowAnyHeader().AllowAnyMethod().AllowCredentials() ); });
主要就是配置來源了,其他引數根據專案需要改配置即可。
以上,是全域性生效的做法,我們也可以選擇部分生效,包括禁用Cors,選擇哪些路由或者控制器、aciont生效。
具體參考微軟官方文件:https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?source=recommendations&view=aspnetcore-7.0
引申點:
有了這個跨域請求,我們就可以不需要使用JSONP來處理了,可直接像JSON一樣即可。
以前JSONP還只能GET請求,對於增刪改操作不合適。
現在有了跨域,也可以採用POST發起ajax請求了。
==========================================
更多分享,請大家關注我的個人公眾號: