Ocelot是一個基於netcore實現的API網關,本質是一組按特定順序排列的中間件。Ocelot內部實現了路由轉發,限流,熔斷,請求聚合,服務發現(集成consul,eureka等),負載均衡,認證(集成Identity)功能。html
這裏簡單介紹下ocelot的配置文件,也就是說如下圖爲例,請求地址爲localhost:18002/users會被轉發到localhost:18000/api/usersandroid
更多關於Ocelot的介紹能夠看https://www.cnblogs.com/jesse2013/p/net-core-apigateway-ocelot-docs.html這篇博客或者https://ocelot.readthedocs.io/en/latest/index.html官方文檔。ios
這裏咱們實現一個Ocelot集成Idnetity作認證的Demo;咱們這裏客戶端請求ocelot網關服務,ocelot網關服務集成Idnetity獲取token,再經過返回的token請求用戶信息服務,以下圖所示。這裏擴展一個知識點,咱們的Identity服務使用擴展認證,這個認證須要實現IExtensionGrantValidator接口的ValidateAsync方法,從http請求上下文中獲取自定義參數,獲取方法爲context.Request.Raw。(oauth2默認的認證方式有password,authcode等,擴展認證文檔=》http://docs.identityserver.io/en/latest/topics/extension_grants.html?highlight=IExtensionGrantValidator)git
首先咱們建立三個服務,分別爲Ocelot網關服務(端口號設置爲18002),Identity認證服務(端口號設置爲18001),UserInfo用戶信息服務(端口號設置爲18000),以下圖所示=》github
1 namespace User.API.Controllers 2 { 3 [Route("api/users")] 4 public class UserController : BaseController 5 { 6 private readonly UserContext _userContext; 7 private ILogger<UserController> _logger; 8 public UserController(UserContext userContext, ILogger<UserController> logger) 9 { 10 _userContext = userContext; 11 _logger = logger; 12 } 13 [HttpGet] 14 public async Task<IActionResult> Get() 15 16 { 17 var user = await _userContext.Set<AppUser>() 18 .AsNoTracking() 19 .Include(u => u.userProperties) 20 .FirstOrDefaultAsync(t => t.Id == 1); 21 if (user == null) 22 { 23 _logger.LogError("登陸用戶爲空"); 24 throw new UserOperationException("用戶登陸異常"); 25 } 26 return Json(user); 27 } 28 ..... other
1 [Route("check_or_create")] 2 [HttpPost] 3 public async Task<IActionResult> CheckOrCreate(string phone) 4 { 5 var user = await _userContext.Users.SingleOrDefaultAsync(u => u.Phone == phone); 6 7 if (user == null) 8 { 9 user = new AppUser { Phone = phone }; 10 _userContext.Users.Add(new AppUser { Phone = phone }); 11 await _userContext.SaveChangesAsync(); 12 } 13 return Ok(new { 14 user.Id, 15 user.Name, 16 user.Company, 17 user.Title, 18 user.Avatar 19 }); 20 }
1 public class Config 2 { 3 public static IEnumerable<Client> GetClients() 4 { 5 return new List<Client>{ 6 new Client{ 7 ClientId = "android", 8 ClientSecrets = new List<Secret> 9 { 10 new Secret("secret".Sha256()) 11 }, 12 RefreshTokenExpiration = TokenExpiration.Sliding, 13 AllowOfflineAccess = true, 14 RequireClientSecret = false, 15 AllowedGrantTypes = new List<string>{"sms_auth_code"}, 16 AlwaysIncludeUserClaimsInIdToken = true, 17 AllowedScopes = new List<string> 18 { 19 "gateway_api", 20 IdentityServerConstants.StandardScopes.OfflineAccess, 21 IdentityServerConstants.StandardScopes.OpenId, 22 IdentityServerConstants.StandardScopes.Profile 23 } 24 } 25 }; 26 } 27 public static IEnumerable<IdentityResource> GetIdentityResources() 28 { 29 return new List<IdentityResource> 30 { 31 new IdentityResources.OpenId(), 32 new IdentityResources.Profile() 33 }; 34 } 35 public static IEnumerable<ApiResource> GetApiResources() 36 { 37 return new List<ApiResource> 38 { 39 new ApiResource("gateway_api","user service") 40 }; 41 } 42 }
編寫咱們的自定義自定義驗證服務類,咱們驗證客戶端傳入的手機號&驗證碼是否正確(Demo邏輯中只須要填寫正確手機號就能夠了)json
1 public class SmsAuthCodeGrantType : IExtensionGrantValidator 2 { 3 private IUserService _userService; 4 private IAuthCodeService _authCodeService; 5 public SmsAuthCodeGrantType(IUserService userService, IAuthCodeService authCodeService) 6 { 7 _userService = userService; 8 _authCodeService = authCodeService; 9 } 10 public string GrantType => "sms_auth_code"; 11 /// <summary> 12 /// 13 /// </summary> 14 /// <param name="context"></param> 15 /// <returns></returns> 16 public async Task ValidateAsync(ExtensionGrantValidationContext context) 17 { 18 var phone = context.Request.Raw["phone"]; 19 var code = context.Request.Raw["auth_code"]; 20 var errorValidationResult = new GrantValidationResult(TokenRequestErrors.InvalidGrant); 21 22 23 if (string.IsNullOrWhiteSpace(phone) || string.IsNullOrWhiteSpace(code)) 24 { 25 context.Result = errorValidationResult; 26 return; 27 } 28 //檢查驗證碼 29 if (!_authCodeService.Validate(phone, code)) 30 { 31 context.Result = errorValidationResult; 32 return; 33 } 34 //完成用戶註冊 35 var userinfo = await _userService.CheckOrCreate(phone); 36 if (userinfo== null) 37 { 38 context.Result = errorValidationResult; 39 return; 40 } 41 var claims = new Claim[] 42 { 43 new Claim("name",userinfo.Name??string.Empty), 44 new Claim("company",userinfo.Company??string.Empty), 45 new Claim("title",userinfo.Tiltle??string.Empty), 46 new Claim("avatar",userinfo.Avatar??string.Empty), 47 }; 48 context.Result = new GrantValidationResult(userinfo.Id.ToString(), 49 GrantType, 50 claims); 51 } 52 }
其餘的驗證服務和與User.API服務通訊的服務類和返回的UserInfoDtoapi
1 public class UserInfo 2 { 3 public int Id { get; set; } 4 public string Name { get; set; } 5 public string Company { get; set; } 6 public string Tiltle { get; set; } 7 public string Avatar { get; set; } 8 }
1 public interface IAuthCodeService 2 { 3 /// <summary> 4 /// 根據手機號驗證驗證碼 5 /// </summary> 6 /// <param name="phone"></param> 7 /// <param name="authCode"></param> 8 /// <returns></returns> 9 bool Validate(string phone, string authCode); 10 }
1 public class TestAuthCodeService : IAuthCodeService 2 { 3 public bool Validate(string phone, string authCode) 4 { 5 return true; 6 } 7 }
1 public interface IUserService 2 { 3 /// <summary> 4 /// 檢查手機號是否註冊,未註冊就註冊 5 /// </summary> 6 /// <param name="phone"></param> 7 Task<UserInfo> CheckOrCreate(string phone); 8 }
1 public class UserService : IUserService 2 { 3 private HttpClient _httpClient; 4 private string _userServiceUrl = "http://localhost:18000"; 5 public UserService(HttpClient httpClient) 6 { 7 _httpClient = httpClient; 8 } 9 10 public async Task<UserInfo> CheckOrCreate(string phone) 11 { 12 var from = new Dictionary<string, string> 13 { 14 { "phone",phone } 15 }; 16 var content = new FormUrlEncodedContent(from); 17 var response = await _httpClient.PostAsync(_userServiceUrl + "/api/users/check_or_create", content); 18 if (response.StatusCode == System.Net.HttpStatusCode.OK) 19 { 20 var result = await response.Content.ReadAsStringAsync(); 21 var userinfo = JsonConvert.DeserializeObject<UserInfo>(result); 22 23 //int.TryParse(userId,out int UserIdInt); 24 return userinfo; 25 } 26 return null; 27 } 28 }
配置Startup,注意要在咱們的DI容器中注入自定義服務驗證類(SmsAuthCodeGrantType)app
1 public class Startup 2 { 3 public Startup(IConfiguration configuration) 4 { 5 Configuration = configuration; 6 } 7 8 public IConfiguration Configuration { get; } 9 10 // This method gets called by the runtime. Use this method to add services to the container. 11 public void ConfigureServices(IServiceCollection services) 12 { 13 services.AddMvc(); 14 services.AddIdentityServer() 15 .AddExtensionGrantValidator<SmsAuthCodeGrantType>() 16 .AddDeveloperSigningCredential() 17 .AddInMemoryClients(Config.GetClients()) 18 .AddInMemoryIdentityResources(Config.GetIdentityResources()) 19 .AddInMemoryApiResources(Config.GetApiResources()); //identityserver 認證 20 21 services.AddScoped<IAuthCodeService, TestAuthCodeService>() 22 .AddScoped<IUserService, UserService>(); 23 services.AddSingleton(new HttpClient()); 24 } 25 26 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 27 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 28 { 29 if (env.IsDevelopment()) 30 { 31 app.UseDeveloperExceptionPage(); 32 } 33 app.UseIdentityServer(); 34 app.UseMvc(); 35 } 36 }
1 { 2 "ReRoutes": [ 3 { 4 "DownstreamPathTemplate": "/api/users", 5 "DownstreamScheme": "http", 6 "DownstreamHostAndPorts": [ 7 { 8 "Host": "localhost", 9 "Port": 18000 10 } 11 ], 12 "UpstreamPathTemplate": "/users", 13 "UpstreamHttpMethod": [ "Get" ], 14 "AuthenticationOptions": { 15 "AuthenticationProviderKey": "finbook", 16 "AllowedScopes": [] 17 } 18 }, 19 { 20 "DownstreamPathTemplate": "/connect/token", 21 "DownstreamScheme": "http", 22 "DownstreamHostAndPorts": [ 23 { 24 "Host": "localhost", 25 "Port": 18001 26 } 27 ], 28 "UpstreamPathTemplate": "/connect/token", 29 "UpstreamHttpMethod": [ "Post" ] 30 } 31 ], 32 "GlobalConfiguration": { 33 "BaseUrl": "http://localhost:18002" 34 } 35 }
將配置文件加載到服務中,修改Program的CreateWebHostBuilder方法負載均衡
1 public class Program 2 { 3 public static void Main(string[] args) 4 { 5 CreateWebHostBuilder(args).Build().Run(); 6 } 7 8 public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 9 WebHost.CreateDefaultBuilder(args) 10 .UseStartup<Startup>() 11 .UseContentRoot(Directory.GetCurrentDirectory()) 12 .ConfigureAppConfiguration((hostingContext, config) => 13 { 14 config 15 .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) 16 //.AddJsonFile("appsettings.json", true, true) 17 //.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) 18 .AddJsonFile("ocelot.json") 19 .AddEnvironmentVariables(); 20 }) 21 .UseUrls("http://+:18002"); 22 }
配置startup,在DI容器中加入Identity自定義認證,加入Ocelot,啓用Ocelot中間件async
1 public class Startup 2 { 3 public Startup(IConfiguration configuration) 4 { 5 Configuration = configuration; 6 } 7 8 public IConfiguration Configuration { get; } 9 10 // This method gets called by the runtime. Use this method to add services to the container. 11 public void ConfigureServices(IServiceCollection services) 12 { 13 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 14 //添加 認證信息 15 var authenticationProviderKey = "finbook"; 16 services.AddAuthentication() 17 .AddIdentityServerAuthentication(authenticationProviderKey, options => 18 { 19 options.Authority = "http://localhost:18001"; 20 options.ApiName = "gateway_api"; 21 options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Both; 22 options.ApiSecret = "secret"; 23 options.RequireHttpsMetadata = false; 24 }); 25 services.AddOcelot(); 26 } 27 28 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 29 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 30 { 31 if (env.IsDevelopment()) 32 { 33 app.UseDeveloperExceptionPage(); 34 } 35 else 36 { 37 // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 38 app.UseHsts(); 39 } 40 //app.UseAuthentication(); 41 app.UseOcelot(); //.Wait() 42 app.UseHttpsRedirection(); 43 app.UseMvc(); 44 } 45 }
首先獲取token,訪問ocelot網關的/connect/token地址,轉發到Idnetity服務,注意下grant_type參數要和Identity服務中的配置相同
接下來根據獲取到的token,請求用戶信息