專案搭建肯定少不了認證和授權,傳統的單體應用基於cookie和session來完成的。
因為http請求是無狀態的,每個請求都是完全獨立的,服務端無法確認當前請求之前是否登陸過。所以第一次請求(登入),伺服器會返回SessionID 返回給瀏覽器,瀏覽器會存於Cookie中,下次請求帶上SessionID.這樣服務端每次拿到SessionID後就去找是否存在對應的會話資訊,判斷過期及後續操作等......
這個授權操作適用於MVC的專案,在分散式的專案中就不行了。session資訊存在不同的服務例項中,在叢集應用中一般都採取輪詢機制,A服務例項儲存了session資訊,B服務例項上沒有這個資訊,請求達到B服務是會返回401的code資訊,但是已經登入過,所以問題就暴露了......
針對此問題,可採取Session共享:將session資訊存入redis中,每個服務例項都從redis中拿session資訊。 會話粘滯:這個可以依靠nginx實現,也就是請求從登入開始,每個請求都會訪問同一個服務例項。第一次登入訪問的服務例項,之後每一次都會訪問這個服務例項。但是這個就脫離了負載均衡策略,用可能100個請求,80個都是A服務接收......
OAuth2.0(協議)
資料所有者告訴系統,同意授權第三方應用進入系統,獲取這些資料。系統從而產生一個短期的進入令牌,用來代替密碼,供第三方應用使用。
規範了下授權的流程,五種模式:
客戶端憑證(client credentials) 密碼式(password) 隱藏式(implicit) 授權碼(authorization-code) 混合式(Hybrid)
IdentityServer4
IdentityServer4(認證授權的中介軟體)是在.Net Core微服務架構中首選的授權認證解決方案。為Asp.Net Core量身定製實現了OpenId Connect和OAuth2.0協議(規範)。
使用IdentityServer4構建鑑權中心
引入NuGet包:IdentityServer4
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer();//新增認證中介軟體 app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); //客戶端模式--怎麼執行Ids4 services.AddIdentityServer()//怎麼處理 .AddDeveloperSigningCredential()//預設開發者證書,每一次啟動證書都會重新整理 .AddInMemoryClients(InitConfig.GetClients())//InMemory 記憶體模式 .AddInMemoryApiResources(InitConfig.GetApiResources());//能訪問啥資源 }
/// <summary> /// 自定義管理資訊 /// </summary> public class InitConfig { /// <summary> /// 定義ApiResource /// 這裡的資源(Resource)指的就是管理的Api /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("UserApi","使用者獲取Api") }; } /// <summary> /// 定義驗證條件的Client /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId="authentication",//客戶端唯一標識 ClientSecrets=new[]{new Secret("auth123456".Sha256()) },//客戶端密碼進行加密 //AllowedGrantTypes=GrantType.ClientCredentials,//驗證模式 AllowedGrantTypes={GrantType.ClientCredentials },//驗證模式 AllowedScopes=new []{ "UserApi"},//作用域,可以訪問的資源,該使用者可訪問哪些Api Claims=new List<Claim>() { new Claim(IdentityModel.JwtClaimTypes.Role,"admin"), new Claim(IdentityModel.JwtClaimTypes.NickName,"江北"), new Claim("Email","**********@163.com"), } } }; } }
使用Postman測試:
在服務例項中加入鑑權中介軟體,引入NuGet包 IdentityServer4.AccessTokenValidation,並在需要鑑權的介面上面標識[Authorize]特性
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication();//鑑權,沒有鑑權,授權是沒有意義的 app.UseAuthorization();//授權 app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); //啟動時註冊,且註冊一次 this.Configuration.ConsulExtend(); }
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(m => { m.Authority = "http://localhost:7200";//Ids的地址 m.ApiName = "GetCustomerUser"; m.RequireHttpsMetadata = false; }); }
直接訪問會報401的錯誤
先去請求鑑權中心,拿到Token後,帶上Token請求
如果報下面這個錯誤,可能是作用域的問題,你請求的Api和可訪問的Api集合對不上
針對上面的過程,我們理一理流程。首先我們在A鑑權服務中心請求獲取Token,拿到Token後去請求B例項服務上的介面。A鑑權中心和B服務例項並沒有進行互動,B服務例項怎樣識別Token,這之間是怎樣的驗證的呢?
其實這裡有一個加密演算法
非對稱可逆加密,加密Key和解密Key不同(一對兒),而且無法推導
加密--content--result(原文加密為密文) 解密--result--content(通過密文解密為原文)
公開解密key-公鑰
私藏加密key-私鑰
再來走一下流程,首先在A鑑權中心驗證登入,並將獲取的使用者資訊用私鑰加密作為Token進行響應返回。所以在Token中不適合存一些敏感資訊。但是有一點可以保證,只要是用這個私鑰配對的公鑰解開的Token,那麼一定是由這個私鑰加密的,就證明這個Token是合法的,可通過的。
那什麼時候拿到公鑰?第一次.沒錯,在第一次請求到達B服務例項的時候,B服務例項會去A鑑權中心請求拿到公鑰。之後就不在需要和A鑑權中心進行互動,除非B服務例項重新啟動。
既然是微服務架構,所以鑑權也統一走閘道器,不然每一個服務例項都要寫一套鑑權的程式碼。因為走閘道器鑑權,所以所有的api都要授權才能訪問。
在閘道器服務中引入包 IdentityServer4.AccessTokenValidation
public void ConfigureServices(IServiceCollection services) { #region Identity4 var authenticationProviderKey = "Gatewaykey"; services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(authenticationProviderKey, m => { m.Authority = "http://localhost:7200";//Ids的地址,獲取公鑰 m.ApiName = "GetCustomerUser"; m.RequireHttpsMetadata = false; m.SupportedTokens = SupportedTokens.Both; }); #endregion }
//*************************Consul+Cache+超時+限流+熔斷+降級***************************** "Routes": [ { //GeteWay轉發=>Downstream "DownstreamPathTemplate": "/api/{url}", //服務地址--url變數 "DownstreamScheme": "http", //http://localhost:6299/T5/User/GetCustomerUser "UpstreamPathTemplate": "/T5/{url}", //閘道器地址--url變數 衝突的還可以加權重Priority "UpstreamHttpMethod": [ "Get", "Post" ], "UseServiceDiscovery": true, //使用服務發現 "ServiceName": "MicroserviceAttempt", //Consul服務名稱 "LoadBalancerOptions": { "Type": "RoundRobin" //輪詢 //"LeastConnection":最少連線數伺服器 "NoloadBalance":不負載均衡 "CookieStickySession":會話粘滯 }, //使用快取 "FileCacheOptions": { "TtlSeconds": 15, //過期時間 "Region": "UserCache" //可以呼叫Api清理 }, //限流 張隊長貢獻的 "RateLimitOptions": { "ClientWhitelist": [ "Microservice", "Attempt" ], //白名單 ClientId區分大小寫 "EnableRateLimiting": true, "Period": "1s", //5m 1h 1d "PeriodTimespan": 30, //多少秒之後客戶端可以重試 "Limit": 5 //統計時間段內允許的最大請求數 },
//鑑權 "AuthenticationOptions": { "AuthenticationProviderKey": "Gatewaykey", "AllowedScopes": [] }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, //熔斷之前允許多少個異常請求 "DurationOfBreak": 10000, //熔斷的時間,單位為ms.超過這個時間可再請求 "TimeoutValue": 4000 //如果下游請求的處理時間超過多少則將請求設定為超時 預設90秒 } } ], "GlobalConfiguration": { "BaseUrl": "http://127.0.0.1:6299", "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul" //由Consul提供服務發現,每次請求去Consul }, "RateLimitOptions": { "QuotaExceededMessage": "Customize Tips!", //限流時返回的訊息 "HttpStatusCode": 999 //限流時返回的code } //"ServiceDiscoveryProvider": { // "Host": "localhost", // "Port": 8500, // "Type": "PollConsul", //由Consul提供服務發現,每次請求去Consul // "PollingInterval": 1000//輪詢Consul,評率毫秒--down是不知道的 //} } //*************************Consul+Cache+超時+限流+熔斷+降級*****************************
啟動鑑權服務、閘道器、服務例項
Postman測試
當然,還不是很完善,後期我會再補充。
如有不當,望包涵!?