項目搭建肯定少不了認證和授權,傳統的單體應用基於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測試
當然,還不是很完善,後期我會再補充。
如有不當,望包涵!😉