A Brief Introduction to Ocelot of asp.net core Gateway & Ocelot Integrated Identity Authentication

Keywords: PHP JSON Mobile Android github

Brief introduction of the article

  • Introduction to Ocelot Gateway

  • Ocelot Integrated Idnetity Authentication Processing

Brief Introduction to Ocelot Gateway

Ocelot is an API gateway based on netcore, which is essentially a set of middleware arranged in a specific order. Ocelot implements routing forwarding, current limiting, fusing, request aggregation, service discovery (integrated consul, eureka, etc.), load balancing, authentication (integrated Identity) functions.

Here's a brief introduction to the ocelot configuration file. That is to say, for example, the following figure shows that the request address is localhost:18002/users will be forwarded to localhost:18000/api/users

More about Ocelot can be seen https://www.cnblogs.com/jesse2013/p/net-core-apigateway-ocelot-docs.html This blog or https://ocelot.readthedocs.io/en/latest/index.html Official documents.

Ocelot Integrated Identity Authentication

Here we implement an Ocelot integrated with Idnetity to authenticate Demo; here the client requests the ocelot gateway service, and the ocelot gateway service integrated with Idnetity obtains the token, and then requests the user information service through the returned token, as shown in the figure below. Extending a knowledge point here, our Identity service uses extended authentication, which requires the implementation of the ValidateAsync method of the IExtension Grant Validator interface to obtain custom parameters from the http request context, using context.Request.Raw. (oauth2 default authentication methods are password, authcode, etc., extended authentication document=) http://docs.identityserver.io/en/latest/topics/extension_grants.html?highlight=IExtensionGrantValidator)

 

First, we create three services: Ocelot Gateway Service (port number set to 1802), Identity Authentication Service (port number set to 1801), UserInfo User Information Service (port number set to 1800), as shown in the following figure=.

 

  • First, we configure the User.API service, which is very simple. It opens a port to return user information. The key code is as follows=.
 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("The logged-in user is empty");
24                 throw new UserOperationException("User login exception");
25             }
26             return Json(user);
27         }
28 ..... other
User.Api
 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         }
User.Api verifies the user's mobile phone number and returns user information
 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     }
Config

Write our custom validation service class, we validate the mobile phone number passed in by the client-validation number is correct (Demo logic only needs to fill in the correct mobile phone number)

 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             //Check the Verification Code
29             if (!_authCodeService.Validate(phone, code))
30             {
31                 context.Result = errorValidationResult;
32                 return;
33             }
34             //Complete user registration
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     }
SmsAuthCodeGrantType

Other authentication services and service classes communicating with User.API services and returned 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     }
UserInfo
 1     public interface IAuthCodeService
 2     {
 3         /// <summary>
 4         /// Verify the validation code according to the mobile phone number
 5         /// </summary>
 6         /// <param name="phone"></param>
 7         /// <param name="authCode"></param>
 8         /// <returns></returns>
 9         bool Validate(string phone, string authCode);
10     }
IAuthCodeService
1     public class TestAuthCodeService : IAuthCodeService
2     {
3         public bool Validate(string phone, string authCode)
4         {
5             return true;
6         }
7     }
TestAuthCodeService
1     public interface IUserService
2     {
3         /// <summary>
4         /// Check whether the mobile phone number is registered or not. Register without registering.
5         /// </summary>
6         /// <param name="phone"></param>
7         Task<UserInfo> CheckOrCreate(string phone);
8     }
IUserService
 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     }
UserService

Configure Startup and note that we need to inject a custom service validation class (SmsAuthCodeGrantType) into our DI container

 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 Authentication
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     }
Startup
 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 }
ocelot.json

Load the configuration file into the service and modify the CreaeWebHostBuilder method of the Program

 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     }
Program

Configure startup, add Identity custom authentication to DI container, add Ocelot, and enable Ocelot Middleware

 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             //Adding authentication information
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     }
Startup

First, get token, access the / connect/token address of the ocelot Gateway, and forward it to the Idnetity service. Note that the grant_type parameter is the same as the configuration in the Identity service.

Next, request user information based on the token obtained

Demo address = https://github.com/madeinchinalmc/User.Api

Posted by clio-stylers on Sun, 21 Jul 2019 08:49:43 -0700