微服務專案整合Ocelot+IdentityServer4

江北、發表於2020-08-22

專案搭建肯定少不了認證和授權,傳統的單體應用基於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測試

 當然,還不是很完善,後期我會再補充。

如有不當,望包涵!?

 

相關文章