微服务项目整合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测试

 当然,还不是很完善,后期我会再补充。

如有不当,望包涵!😉

 

posted @ 2020-08-22 15:11  江北、  阅读(2779)  评论(4编辑  收藏  举报