文章简介
-
Ocelot网关简介
-
Ocelot集成Idnetity认证处理
Ocelot网关简介
Ocelot是一个基于netcore实现的API网关,本质是一组按特定顺序排列的中间件。Ocelot内部实现了路由转发,限流,熔断,请求聚合,服务发现(集成consul,eureka等),负载均衡,认证(集成Identity)功能。
这里简单介绍下ocelot的配置文件,也就是说以下图为例,请求地址为localhost:18002/users会被转发到localhost:18000/api/users
更多关于Ocelot的介绍可以看https://www.cnblogs.com/jesse2013/p/net-core-apigateway-ocelot-docs.html这篇博客或者https://ocelot.readthedocs.io/en/latest/index.html官方文档。
Ocelot集成Identity认证
这里我们实现一个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)
首先我们创建三个服务,分别为Ocelot网关服务(端口号设置为18002),Identity认证服务(端口号设置为18001),UserInfo用户信息服务(端口号设置为18000),如下图所示=》
- 首先我们配置User.API服务,这个服务很简单,开放一个返回用户信息的端口,关键代码如下所示 =》
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 }
- 然后配置我们的Identity认证服务,引入IdnttiyServer4 nuget包,添加Ids4配置文件Config.cs。注意:这里的client_grant_type为sms_auth_code
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逻辑中只需要填写正确手机号就可以了)
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服务通信的服务类和返回的UserInfoDto
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)
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 }
- 最后配置我们的网关Ocelot站点,首先添加nuget包IdentityServer4.AccessTokenValidation和Ocelot。添加配置文件ocelot.json,其实就是博客开头的配置文件截图,这里特别说明下AuthenticationOptions节点,AuthenticationOptions是ocelot集成Identity所需要配置节点,AuthenticationProviderKey需要跟startup的authenticationScheme匹配
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中间件
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,请求用户信息