微服務項目整合Ocelot+IdentityServer4

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

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

如有不當,望包涵!😉

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章