NetCore Ocelot

     Ocelot是一个用.NET Core实现并且开源的API网关,功能包括:路由,请求聚合,服务验证,鉴权,限流熔断,并内置了负载均衡器与Service Fabric,Buttefly Tracing集成。这些功能都只需要简单配置就可以完成。

    简单的ocelot是一堆asp.net core middleware组成的一个管道,当它拿到请求之后会用一个request builder来构造一个HttpRequestMessage发到下游的真实服务器,等下游的服务返回response之后再由一个middleware将它返回的HttpResponseMessage映射到HttpResponse上.

  

 

 

 


网关集群
只有一个网关是很危险的,也就是我们通常所将的单点,只要它挂了,所有的服务全挂。这显然无法达到高可用,所以我们也可以部署多台网关。当然这个时候再多台网关前,你还需要一台负载均衡器。

 

 

 


Consul 服务发现
在Ocelot已经支持简单的负载均衡功能,也就是当下游服务存在多个结点的时候,Ocelot能够承担起负载均衡的作用。但是它不提供健康检查,服务的注册也只能通过手动子配置文件里面添加完成。这个时候我们就可以用Consul来做服务发现,它能与Ocelot完美结合

 

 

 

 下面实战

ocelot配置信息, 这里ocelot.json是系统生成的,code中实现了多配置文件合并功能

 1、ocelot.consul.json

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "https",
      "DownstreamHttpMethod": "Get",
      "UpstreamHttpMethod": [ "Options", "Get", "Post", "Put", "Delete" ],
      "UpstreamPathTemplate": "/Ocelot/{url}",
      //"UpstreamHost": "localhost",//404 Not found
      "UseServiceDiscovery": true,
      "ServiceName": "serviceA",
      /*
      LeastConnection
      RoundRobin
      NoLoadBalance
      */
      "LoadBalancerOptions": {
        "Type": "CustomRandomLoadBalancer"
      },
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3,
        "DurationOfBreak": 10000, //ms
        "TimeoutValue": 10000 //ms,default 90s
      },
      "RateLimitOptions": {
        "ClientWhiteList": [ "NoLimitedAPI" ],
        "EnableRateLimiting": true,
        "Period": "10s", //1s, 5m, 1h, 1d
        "PeriodTimespan": 10, //retry after n second/seconds
        "Limit": 3
      },
      "authenticationoptions": {
        "authenticationproviderkey": "authenticationkey",
        "allowedscopes": []
      },
      //"routeclaimsrequirement": {
      //  "userrole": "admin"
      //},
      //"DownstreamHttpVersion": "",
      //"AddHeadersToRequest": {},
      //"AddClaimsToRequest": {},
      //"AddQueriesToRequest": {},
      //"RequestIdKey": "",
      //"FileCacheOptions": {
      //  "TtlSeconds": 10,
      //  "Region": "gatewaycacheregion"
      //},
      //"HttpHandlerOptions": {
      //  "AllowAutoRedirect": true,
      //  "UseCookieContainer": false,
      //  "UseTracing": true,
      //  "MaxConnectionsPerServer": 100
      //},
      //"DangerousAcceptAnyServerCertificateValidator": false,
      "Priority": 10 //priority
    }
  ]
}
ocelot.consul
 1 {
 2   "GlobalConfiguration": {
 3     "BaseUrl": "https://localhost:7000", //gateway public address
 4     "RequestIdKey": "OcRequestId",
 5     "RouteIsCaseSensitive": false,
 6     "ServiceDiscoveryProvider": {
 7       "Host": "127.0.0.1",
 8       "Port": 8500,
 9       "Type": "Consul",
10       "ConfigurationKey": "ApiGateway.Ocelot"
11     },
12     "RateLimitOptions": {
13       "ClientIdHeader": "IgnoreRateLimit", //request header key
14       "QuotaExceededMessage": "The requests has reached the quota.", //The customized prompt
15       "RateLimitCounterPrefix": "ocelotratelimit",
16       "DisableRateLimitHeaders": false,
17       "HttpStatusCode": 666
18     }
19   }
20 }
ocelot.global
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/{url}",
      "UpstreamPathTemplate": "/Ocelot/{url}",
      "UpstreamHttpMethod": [ "Options", "Get", "Post", "Put", "Delete" ],
      "DownstreamHttpMethod": "Get",
      "AddHeadersToRequest": {},
      "UpstreamHeaderTransform": {},
      "DownstreamHeaderTransform": {},
      "AddClaimsToRequest": {},
      "RouteClaimsRequirement": { "userrole": "admin" },
      "AddQueriesToRequest": {},
      "ChangeDownstreamPathTemplate": {},
      "RequestIdKey": null,
      "FileCacheOptions": {
        "TtlSeconds": 0,
        "Region": null
      },
      "RouteIsCaseSensitive": false,
      "ServiceName": "serviceA",
      "ServiceNamespace": null,
      "DownstreamScheme": "https",
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3,
        "DurationOfBreak": 10000,
        "TimeoutValue": 10000
      },
      "LoadBalancerOptions": {
        "Type": "CustomRandomLoadBalancer",
        "Key": null,
        "Expiry": 0
      },
      "RateLimitOptions": {
        "ClientWhitelist": [ "NoLimitedAPI" ],
        "EnableRateLimiting": true,
        "Period": "10s",
        "PeriodTimespan": 10.0,
        "Limit": 3
      },
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "authenticationkey",
        "AllowedScopes": []
      },
      "HttpHandlerOptions": {
        "AllowAutoRedirect": false,
        "UseCookieContainer": false,
        "UseTracing": false,
        "UseProxy": true,
        "MaxConnectionsPerServer": 2147483647
      },
      "DownstreamHostAndPorts": [],
      "UpstreamHost": null,
      "Key": null,
      "DelegatingHandlers": [],
      "Priority": 10,
      "Timeout": 0,
      "DangerousAcceptAnyServerCertificateValidator": false,
      "SecurityOptions": {
        "IPAllowedList": [],
        "IPBlockedList": []
      },
      "DownstreamHttpVersion": null
    },
    {
      "DownstreamPathTemplate": "/api/service1",
      "UpstreamPathTemplate": "/Ocelot/service1",
      "UpstreamHttpMethod": [ "Options", "Get", "Post", "Put", "Delete" ],
      "DownstreamHttpMethod": "Get",
      "AddHeadersToRequest": {},
      "UpstreamHeaderTransform": {},
      "DownstreamHeaderTransform": {},
      "AddClaimsToRequest": {},
      "RouteClaimsRequirement": {},
      "AddQueriesToRequest": {},
      "ChangeDownstreamPathTemplate": {},
      "RequestIdKey": null,
      "FileCacheOptions": {
        "TtlSeconds": 0,
        "Region": null
      },
      "RouteIsCaseSensitive": false,
      "ServiceName": "serviceA",
      "ServiceNamespace": null,
      "DownstreamScheme": "https",
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 0,
        "DurationOfBreak": 0,
        "TimeoutValue": 0
      },
      "LoadBalancerOptions": {
        "Type": "CustomRandomLoadBalancer",
        "Key": null,
        "Expiry": 0
      },
      "RateLimitOptions": {
        "ClientWhitelist": [],
        "EnableRateLimiting": false,
        "Period": null,
        "PeriodTimespan": 0.0,
        "Limit": 0
      },
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "authenticationkey",
        "AllowedScopes": []
      },
      "HttpHandlerOptions": {
        "AllowAutoRedirect": false,
        "UseCookieContainer": false,
        "UseTracing": false,
        "UseProxy": true,
        "MaxConnectionsPerServer": 2147483647
      },
      "DownstreamHostAndPorts": [],
      "UpstreamHost": null,
      "Key": "service1",
      "DelegatingHandlers": [],
      "Priority": 11,
      "Timeout": 0,
      "DangerousAcceptAnyServerCertificateValidator": false,
      "SecurityOptions": {
        "IPAllowedList": [],
        "IPBlockedList": []
      },
      "DownstreamHttpVersion": null
    },
    {
      "DownstreamPathTemplate": "/api/service2",
      "UpstreamPathTemplate": "/Ocelot/service2",
      "UpstreamHttpMethod": [ "Options", "Get", "Post", "Put", "Delete" ],
      "DownstreamHttpMethod": "Get",
      "AddHeadersToRequest": {},
      "UpstreamHeaderTransform": {},
      "DownstreamHeaderTransform": {},
      "AddClaimsToRequest": {},
      "RouteClaimsRequirement": {},
      "AddQueriesToRequest": {},
      "ChangeDownstreamPathTemplate": {},
      "RequestIdKey": null,
      "FileCacheOptions": {
        "TtlSeconds": 0,
        "Region": null
      },
      "RouteIsCaseSensitive": false,
      "ServiceName": "serviceA",
      "ServiceNamespace": null,
      "DownstreamScheme": "https",
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 0,
        "DurationOfBreak": 0,
        "TimeoutValue": 0
      },
      "LoadBalancerOptions": {
        "Type": "CustomRandomLoadBalancer",
        "Key": null,
        "Expiry": 0
      },
      "RateLimitOptions": {
        "ClientWhitelist": [],
        "EnableRateLimiting": false,
        "Period": null,
        "PeriodTimespan": 0.0,
        "Limit": 0
      },
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "authenticationkey",
        "AllowedScopes": []
      },
      "HttpHandlerOptions": {
        "AllowAutoRedirect": false,
        "UseCookieContainer": false,
        "UseTracing": false,
        "UseProxy": true,
        "MaxConnectionsPerServer": 2147483647
      },
      "DownstreamHostAndPorts": [],
      "UpstreamHost": null,
      "Key": "service2",
      "DelegatingHandlers": [],
      "Priority": 12,
      "Timeout": 0,
      "DangerousAcceptAnyServerCertificateValidator": false,
      "SecurityOptions": {
        "IPAllowedList": [],
        "IPBlockedList": []
      },
      "DownstreamHttpVersion": null
    }
  ],
  "DynamicRoutes": [],
  "Aggregates": [
    {
      "RouteKeys": [ "service1", "service2" ],
      "RouteKeysConfig": null,
      "UpstreamPathTemplate": "/api/aggregate",
      "UpstreamHost": null,
      "RouteIsCaseSensitive": false,
      "Aggregator": null,
      "UpstreamHttpMethod": [ "Get" ],
      "Priority": 1
    }
  ],
  "GlobalConfiguration": {
    "RequestIdKey": "OcRequestId",
    "ServiceDiscoveryProvider": {
      "Scheme": null,
      "Host": "127.0.0.1",
      "Port": 8500,
      "Type": "Consul",
      "Token": null,
      "ConfigurationKey": "ApiGateway.Ocelot",
      "PollingInterval": 0,
      "Namespace": null
    },
    "RateLimitOptions": {
      "ClientIdHeader": "IgnoreRateLimit",
      "QuotaExceededMessage": "The requests has reached the quota.",
      "RateLimitCounterPrefix": "ocelotratelimit",
      "DisableRateLimitHeaders": false,
      "HttpStatusCode": 666
    },
    "QoSOptions": {
      "ExceptionsAllowedBeforeBreaking": 0,
      "DurationOfBreak": 0,
      "TimeoutValue": 0
    },
    "BaseUrl": "https://localhost:7000",
    "LoadBalancerOptions": {
      "Type": null,
      "Key": null,
      "Expiry": 0
    },
    "DownstreamScheme": null,
    "HttpHandlerOptions": {
      "AllowAutoRedirect": false,
      "UseCookieContainer": false,
      "UseTracing": false,
      "UseProxy": true,
      "MaxConnectionsPerServer": 2147483647
    },
    "DownstreamHttpVersion": null
  }}
Multiple configuration merged

2、Add ocelot

1 builder.Services.AddOcelot()
2     .AddConsul()
3     .AddPolly();
4 
5 app.UseOcelot().Wait();
ocelot

3、添加自定义load balancer

1 builder.Services.AddOcelot()
2     .AddCustomLoadBalancer((serviceProvider, route, serviceDiscoveryProvider) => new CustomRandomLoadBalancer(serviceDiscoveryProvider.Get))
3     .AddConsul()
4     .AddPolly();
custom load balancer
 1     public class CustomRandomLoadBalancer : ILoadBalancer
 2     {
 3         private readonly Func<Task<List<Service>>> _services;
 4         private readonly Object _lock = new object();
 5         private int _index;
 6 
 7         public CustomRandomLoadBalancer(Func<Task<List<Service>>> services)
 8         {
 9             _services = services;
10         }
11 
12         public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
13         {
14            var services = await _services();
15             if (services == null)
16                 return new ErrorResponse<ServiceHostAndPort>(new ErrorInvokingLoadBalancerCreator(new Exception("Load balance algorithm error.")));
17             lock (_lock)
18             {
19                 if (services.Count == 1)
20                     return new OkResponse<ServiceHostAndPort>(services[0].HostAndPort);
21                 _index = new Random().Next(services.Count);
22                 return new OkResponse<ServiceHostAndPort>(services[_index].HostAndPort);
23             }
24         }
25 
26         public void Release(ServiceHostAndPort hostAndPort)
27         {
28         }
29     }
load balancer

4、添加中间件,设置特定header,避免限流

1 app.Use((httpContext, requestDelegate) =>
2 {
3     if (!httpContext.Request.Headers.TryGetValue("IgnoreRateLimit", out Microsoft.Extensions.Primitives.StringValues value))
4         httpContext.Request.Headers.Add("IgnoreRateLimit", "NoLimitedAPI");
5     return requestDelegate.Invoke(httpContext);
6 });
middleware

添加中间件另种方式

app.UseMiddleware<CustomMiddleware>(3);
middleware
 1    public class CustomMiddleware
 2     {
 3         private readonly RequestDelegate _requestDelegate;
 4         private Int32 _parameter { get; set; }
 5         public CustomMiddleware(RequestDelegate requestDelegate, Int32 parameter)
 6         {
 7             this._requestDelegate = requestDelegate;
 8             this._parameter = parameter;
 9         }
10 
11         public async Task InvokeAsync(HttpContext httpContext)
12         {
13             var argument = _parameter;
14             await _requestDelegate(httpContext);
15         }
16     }
custom middleware

5、集成identity server4

  identity server4 config 配置信息

 1     public class Config
 2     {
 3 
 4         public static IEnumerable<ApiScope> ApiScopes
 5             => new List<ApiScope> 
 6             { 
 7                 new ApiScope("apiscope1", "scope1"),
 8                 new ApiScope("apiscope2","scope2")
 9             };
10 
11         public static IEnumerable<ApiResource> ApiResources
12             => new List<ApiResource>
13             {
14                 new ApiResource("service1","this is service1")
15                 {
16                     Scopes= { "apiscope1" }
17                 },
18                 new ApiResource("service2","this is service2")
19                 {
20                     Scopes ={ "apiscope2" }
21                 }
22             };
23 
24         public static IEnumerable<Client> Clients 
25             => new List<Client> 
26             {
27                 new Client
28                 {
29                     ClientId = "clientId",
30                     ClientSecrets = new [] { new Secret("auth123456".Sha256()) },
31                     AllowedGrantTypes = { GrantType.ClientCredentials },
32                     AllowedScopes = new []
33                     {
34                         "apiscope1",
35                         "apiscope2",
36                         IdentityServerConstants.StandardScopes.OpenId,
37                         IdentityServerConstants.StandardScopes.Address,
38                         IdentityServerConstants.StandardScopes.Email,
39                         IdentityServerConstants.StandardScopes.Phone,
40                         IdentityServerConstants.StandardScopes.Profile
41                     },
42                     Claims = new List<ClientClaim>
43                     {
44                         new ClientClaim(IdentityModel.JwtClaimTypes.Role,"admin"),
45                         new ClientClaim(IdentityModel.JwtClaimTypes.NickName,"tom"),
46                         new ClientClaim("Emai","tom@163.com")
47                     }
48                 }
49             };
50 
51         public static IEnumerable<IdentityResource> IdentityResources
52             => new List<IdentityResource>
53             {
54                 new IdentityResources.OpenId(),
55                 new IdentityResources.Profile(),
56                 new IdentityResources.Address(),
57                 new IdentityResources.Email(),
58                 new IdentityResources.Phone()
59             };
60     }
ids4 configuration
1 builder.Services.AddIdentityServer()
2     .AddDeveloperSigningCredential()
3     .AddInMemoryIdentityResources(Config.IdentityResources)
4     .AddInMemoryApiResources(Config.ApiResources)
5     .AddInMemoryApiScopes(Config.ApiScopes)
6     .AddInMemoryClients(Config.Clients);
7 
8 app.UseIdentityServer();
Ids4 configure service

  启动Ids4 dotnet run

6、ocelot中配置支持Ids4 authentication

 1 var authenticationProviderKey = "authenticationkey";
 2 builder.Services
 3     .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
 4     .AddIdentityServerAuthentication(authenticationProviderKey, options =>
 5     {
 6         options.Authority = "https://localhost:9000";
 7         options.RequireHttpsMetadata = false;
 8         options.ApiName = "service1";
 9         options.SupportedTokens = SupportedTokens.Both;
10         options.JwtBearerEvents = new JwtBearerEvents  
11         {
12             OnChallenge =ctx => 
13             {
14                 return Task.CompletedTask;
15             },
16             OnMessageReceived = ctx => 
17             {
18                 var token = ctx.Token;
19                 return Task.CompletedTask;
20             },
21             OnTokenValidated = ctx => 
22             {
23                 var securityToken = ctx.SecurityToken as JwtSecurityToken;
24                 var claimIdentities = ctx.Principal?.Identities;
25                 if (claimIdentities?.Any(i => i.Claims.Any(c=>c.Value.Equals("admin", StringComparison.OrdinalIgnoreCase))) ?? false)
26                     ctx.Success();
27                 else
28                     ctx.Fail("token validate failed.");
29                 return Task.CompletedTask;
30             },
31             OnAuthenticationFailed = context =>
32             {
33                 context.Fail(context.Exception);
34                 return Task.CompletedTask;
35             },
36             OnForbidden = context =>
37             {
38                 context.Fail("403 forbidden");
39                 return Task.CompletedTask;
40             }
41         };
42     });
Ocelot Ids4
app.UseAuthentication();
use authentication

7、为了避免恶意攻击避开ocelot直接访问service,在netcore api项目中同样需要添加验证

 1 builder.Services.AddAuthentication(options =>
 2 {
 3     options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
 4     options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
 5 }).AddIdentityServerAuthentication(options =>
 6 {
 7     options.Authority = "https://localhost:9000";
 8     options.ApiName = "service1";
 9     options.RequireHttpsMetadata = false;
10     options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Both;
11     options.Events = new JwtBearerEvents
12     {
13         OnTokenValidated = context =>
14         {
15             var token = context.SecurityToken as JwtSecurityToken;
16             var identity = context.Principal?.Identity as ClaimsIdentity;
17             if (!string.IsNullOrEmpty(token?.Issuer))
18                 context.Success();
19             else
20                 context.Fail($"Token invalid");
21             return Task.CompletedTask;
22         },
23         OnAuthenticationFailed = context =>
24         {
25             context.Fail(context.Exception);
26             return Task.CompletedTask;
27         },
28         OnForbidden = context =>
29         {
30             context.Fail("403 forbidden");
31             return Task.CompletedTask;
32         },
33         OnChallenge = context =>
34         {
35             var error = context.Error;
36             return Task.CompletedTask;
37         },
38         OnMessageReceived = context =>
39         {
40             var token = context.Token;
41             return Task.CompletedTask;
42         }
43     };
44 });
NetCore API authentication

 

配置详解

负载均衡

   /*
      LeastConnection
      RoundRobin
      NoLoadBalance
      */
      "LoadBalancerOptions": {
        "Type": "CustomRandomLoadBalancer"
      }

熔断,超时

 "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3,
        "DurationOfBreak": 10000, //ms
        "TimeoutValue": 10000 //ms,default 90s
      }

限流

 "RateLimitOptions": {
        "ClientWhiteList": [ "NoLimitedAPI" ],
        "EnableRateLimiting": true,
        "Period": "10s", //1s, 5m, 1h, 1d
        "PeriodTimespan": 10, //retry after n second/seconds
        "Limit": 3
      }

认证

"authenticationoptions": {
        "authenticationproviderkey": "authenticationkey",
        "allowedscopes": []
      }

Consul服务发现

"ServiceDiscoveryProvider": {
      "Host": "127.0.0.1",
      "Port": 8500,
      "Type": "Consul",
      "ConfigurationKey": "ApiGateway.Ocelot"
    }

 Ocelot缓存

"FileCacheOptions": {
        "TtlSeconds": 10,
        "Region": "gatewaycacheregion"
     }
cache

 

最终效果,通过访问ocelot 定义url 负载到具体的服务实例

token

public class TokenController : Controller
    {
        [HttpGet("accesstoken")]
        public async Task<IActionResult> Index()
        {
            var request = new ClientCredentialsTokenRequest
            {
                Address = "https://localhost:9000/connect/token",
                ClientId = "clientId",
                ClientSecret = "auth123456",
            };
            var client = new HttpClient();
            var disco = await client.GetDiscoveryDocumentAsync("https://localhost:9000");
            if (disco.IsError)
            {
                return StatusCode(500);
            }

            var tokenResponse = await client.RequestClientCredentialsTokenAsync(request);
            if(tokenResponse.IsError)
            {
                return StatusCode(500);
            }
            return Ok(tokenResponse.AccessToken);
        }
    }
Get token from Ids4

 

 

 ocelot api

 

 

 

 

OK, 时间紧迫,有时间详搞。

posted @ 2023-03-04 09:36  云霄宇霁  阅读(188)  评论(0编辑  收藏  举报