Ocelot网关搭建
- 搭建core控制台项目 本demo采用2.1版本 命名为APPIGateWay
- 在Nuget包中添加Ocelot引用 选用8.0.0版本
- 添加Ocelot.json 文件 内容为:
{ "ReRoutes": [ { "DownstreamPathTemplate": "/api/{url}",//下游地址万能模板 "DownstreamScheme": "http", //下游地址以及端口号 //配置多个是为了实现负载均衡功能 //注:最好使用IP 而不是localhost "DownstreamHostAndPorts": [ { "Host": "192.168.1.100", "Port": 21001 }, { "Host": "192.168.1.100", "Port": 21002 } ], "UpstreamPathTemplate": "/api/{url}",//上游地址万能模板 "UpstreamHttpMethod": [ "Get", "POST" ],//转发的请求类型 "LoadBalancer": "LeastConnection",//负载均衡模式 //服务名称 需要和consul中配置的服务名称一致 "ServiceName": "TestService", "UseServiceDiscovery": true,//是否启用服务发现 //身份验证 所需属性 不验证可去除 "AuthenticationOptions": { "AuthenticationProviderKey": "usergateway",//自定义key "AllowScopes": [ "TestService" ]//服务名称 } }, //下面为身份验证服务配置 { "DownstreamPathTemplate": "/connect/token", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "192.168.1.100", "Port": 5100 } ], "UpstreamPathTemplate": "/connect/token", "UpstreamHttpMethod": [ "Get", "POST" ], "LoadBalancer": "LeastConnection", "ServiceName": "IdentityService", "UseServiceDiscovery": true, "AuthenticationOptions": { } } ], //Ocelot全局配置 "GlobalConfiguration": { "BaseUrl": "http://192.168.1.100:5000",//对外地址 //服务发现地址配置 "ServiceDiscoveryProvider": { "Host": "192.168.1.100", "Port": 8500 } } }
- 修改Program.cs
using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using System; namespace APPIGateWay { class Program { static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, builder) => { builder .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("Ocelot.json");//添加配置文件 }) .UseStartup<Startup>() .UseUrls("http://192.168.1.100:5000")//使用指定url .Build(); } }
- 修改Startup.cs
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; using Ocelot.Middleware; using System; using System.Collections.Generic; using System.Text; namespace APPIGateWay { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //services.AddMvc(); -- no need MVC // Ocelot services.AddOcelot(Configuration); //添加验证逻辑 services.AddAuthentication() .AddIdentityServerAuthentication("usergateway", options => { options.Authority = "http://192.168.1.100:5100"; options.ApiName = "TestService"; options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Both; options.ApiSecret = "test"; options.RequireHttpsMetadata = false; }); //增加日志 //.AddOpenTracing(option => //{ // //this is the url that the butterfly collector server is running on... // option.CollectorUrl = "http://localhost:9618"; // option.Service = "Ocelot"; //}); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //app.UseMvc(); -- no need MVC // Ocelot app.UseOcelot().Wait(); } } }
至此 网关部分搭建完毕
添加Consul注册帮助类
- 创建core 类库项目
- 添加以下代码
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; namespace ConsulRegisterHelper { public class NetworkHelper { public static string LocalIPAddress { get { UnicastIPAddressInformation mostSuitableIp = null; var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (var network in networkInterfaces) { if (network.OperationalStatus != OperationalStatus.Up) continue; var properties = network.GetIPProperties(); if (properties.GatewayAddresses.Count == 0) continue; foreach (var address in properties.UnicastAddresses) { if (address.Address.AddressFamily != AddressFamily.InterNetwork) continue; if (IPAddress.IsLoopback(address.Address)) continue; return address.Address.ToString(); } } return mostSuitableIp != null ? mostSuitableIp.Address.ToString() : ""; } } public static int GetRandomAvaliablePort(int minPort = 1024, int maxPort = 65535) { Random rand = new Random(); while (true) { int port = rand.Next(minPort, maxPort); if (!IsPortInUsed(port)) { return port; } } } private static bool IsPortInUsed(int port) { IPGlobalProperties ipGlobalProps = IPGlobalProperties.GetIPGlobalProperties(); IPEndPoint[] ipsTCP = ipGlobalProps.GetActiveTcpListeners(); if (ipsTCP.Any(p => p.Port == port)) { return true; } IPEndPoint[] ipsUDP = ipGlobalProps.GetActiveUdpListeners(); if (ipsUDP.Any(p => p.Port == port)) { return true; } TcpConnectionInformation[] tcpConnInfos = ipGlobalProps.GetActiveTcpConnections(); if (tcpConnInfos.Any(conn => conn.LocalEndPoint.Port == port)) { return true; } return false; } } }
using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using System; namespace ConsulRegisterHelper { public static class AppBuilderExtensions { public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IApplicationLifetime lifetime, ServiceEntity serviceEntity) { var consulClient = new ConsulClient(x => x.Address = new Uri($"http://{serviceEntity.ConsulIP}:{serviceEntity.ConsulPort}"));//请求注册的 Consul 地址 var httpCheck = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册 Interval = TimeSpan.FromSeconds(3),//健康检查时间间隔,或者称为心跳间隔 HTTP = $"http://{serviceEntity.IP}:{serviceEntity.Port}{serviceEntity.HealthUrl}",//健康检查地址 Timeout = TimeSpan.FromSeconds(3) }; // Register service with consul var registration = new AgentServiceRegistration() { Checks = new[] { httpCheck }, ID = Guid.NewGuid().ToString(), Name = serviceEntity.ServiceName, Address = serviceEntity.IP, Port = serviceEntity.Port, Tags = new[] { $"urlprefix-/{serviceEntity.ServiceName}" }//添加 urlprefix-/servicename 格式的 tag 标签,以便 Fabio 识别 }; consulClient.Agent.ServiceRegister(registration).Wait();//服务启动时注册,内部实现其实就是使用 Consul API 进行注册(HttpClient发起) lifetime.ApplicationStopping.Register(() => { consulClient.Agent.ServiceDeregister(registration.ID).Wait();//服务停止时取消注册 }); return app; } } }
using System; using System.Collections.Generic; using System.Text; namespace ConsulRegisterHelper { public class ServiceEntity { public ServiceEntity() { HealthUrl = "/api/health"; } /// <summary> /// 服务IP /// </summary> public string IP { get; set; } /// <summary> /// 服务端口号 /// </summary> public int Port { get; set; } /// <summary> /// 服务名称 /// </summary> public string ServiceName { get; set; } /// <summary> /// 服务发现地址 /// </summary> public string ConsulIP { get; set; } /// <summary> /// 服务发现端口号 /// </summary> public int ConsulPort { get; set; } /// <summary> /// 健康检查地址默认为/api/health /// </summary> public string HealthUrl { get; set; } } }
至此帮助类搭建完毕 该帮助类主要是为了方便服务注册使用
身份验证服务搭建
- 新建空web core项目 命名IdentityService
- 下载Quickstart 发布版放入
- 添加配置文件 appsettings.json
{ "Service": { "Name": "IdentityService", "Port": "5100" }, "Consul": { "IP": "localhost", "Port": "8500" }, "Logging": { "IncludeScopes": false, "Debug": { "LogLevel": { "Default": "Warning" } }, "Console": { "LogLevel": { "Default": "Warning" } } } }
- 添加健康检查控制器
- 添加以下文件
using IdentityServer4.Models; using IdentityServer4.Test; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace IdentityService { //测试使用内容 public class InMemoryConfiguration { public static IConfiguration Configuration { get; set; } /// <summary> /// Define which APIs will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("TestService", "测试服务1"), new ApiResource("TestService2", "测试服务2") }; } /// <summary> /// Define which Apps will use thie IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId = "c1", ClientSecrets = new [] { new Secret("secret1".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "TestService", "TestService2" } }, new Client { ClientId = "c-low", ClientSecrets = new [] { new Secret("clowsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "TestService" } } }; } } }
using IdentityServer4.Models; using IdentityServer4.Services; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace IdentityService { public class ProfileService : IProfileService { public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var claims = context.Subject.Claims.ToList(); context.IssuedClaims = claims.ToList(); } public async Task IsActiveAsync(IsActiveContext context) { context.IsActive = true; } } }
using IdentityServer4.Models; using IdentityServer4.Validation; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; namespace IdentityService { public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { //ToDo:验证自定义用户 //LoginUser loginUser = null; bool isAuthenticated = context.UserName=="aaa"&&context.Password=="1"? true :false; //loginUserService.Authenticate(context.UserName, context.Password, out loginUser); if (!isAuthenticated) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "账户名密码错误"); } else { context.Result = new GrantValidationResult( subject: context.UserName, authenticationMethod: "custom", claims: new Claim[] { new Claim("Name", context.UserName), new Claim("Id", ""), new Claim("RealName", ""), new Claim("Email", "") } ); } return Task.CompletedTask; } } }
- 修改以下文件
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace IdentityService { public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseUrls("http://192.168.1.100:5100") .Build(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using ConsulRegisterHelper; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.PlatformAbstractions; namespace IdentityService { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); InMemoryConfiguration.Configuration = this.Configuration; services.AddIdentityServer() .AddDeveloperSigningCredential()//开发临时证书 .AddInMemoryClients(InMemoryConfiguration.GetClients()) .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources()) .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()//添加自定义验证 .AddProfileService<ProfileService>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // authentication app.UseMvc(); app.UseIdentityServer(); //启用UI app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); // register this service app.RegisterConsul(lifetime, new ServiceEntity { IP = NetworkHelper.LocalIPAddress, Port = Convert.ToInt32(Configuration["Service:Port"]), ServiceName = Configuration["Service:Name"], ConsulIP = Configuration["Consul:IP"], ConsulPort = Convert.ToInt32(Configuration["Consul:Port"]) }); } } }
身份验证服务搭建完毕
测试服务搭建
- 创建TestService1 模板为coreAPi
- 引用之前创建的ConsulRegisterHelper
- 添加HealthController.cs
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace TestService1.Controllers { [Route("api/[controller]")] public class HealthController : Controller { // GET api/values [HttpGet] public string Get() { return "ok"; } } }
- 修改Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); // register this service app.RegisterConsul(lifetime, new ServiceEntity { IP = NetworkHelper.LocalIPAddress, Port = Convert.ToInt32(Configuration["Service:Port"]), ServiceName = Configuration["Service:Name"], ConsulIP = Configuration["Consul:IP"], ConsulPort = Convert.ToInt32(Configuration["Consul:Port"]) }); }
搭建完毕
其他服务类似于此服务
搭建完毕后可用http://localhost:5000/connect/token 访问获取token
需要传入 这些参数。
获取到token后 可访问 http://localhost:5000/api/values 获取方法内容
需要在headers中加入 Authorization 并附上定义的前缀+空格+token 即可请求到数据
附上结果图
参考博客:
微服务系列博客
https://www.cnblogs.com/edisonchou/p/dotnetcore_microservice_foundation_blogs_index.html
token权限控制
https://www.cnblogs.com/jaycewu/p/7791102.html
Ocelot+identity
http://www.cnblogs.com/liyouming/p/9025084.html