{ "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 } } }
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(); } }
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(); } } }
至此 網關部分搭建完畢html
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; } } }
至此幫助類搭建完畢 該幫助類主要是爲了方便服務註冊使用git
{ "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"]) }); } } }
身份驗證服務搭建完畢github
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"; } } }
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"]) }); }
搭建完畢web
其餘服務相似於此服務json
搭建完畢後可用http://localhost:5000/connect/token 訪問獲取token api
須要傳入 這些參數。app
獲取到token後 可訪問 http://localhost:5000/api/values 獲取方法內容負載均衡
須要在headers中加入 Authorization 並附上定義的前綴+空格+token 便可請求到數據 dom
附上結果圖async
參考博客:
微服務系列博客
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