简体   繁体   中英

Custom JWT token validation in .NET Core 2.2

I'm trying to validate a JWT token with a custom validation. The token is generated in another API than my own, but I have a way to validate it against a service.

I'm at a loss as to what is missing... I keep getting 401 codes, even when my validation is correct.

EDIT : Added token validations parameters and added precisions about token validation

Here is my code so far:

Startup.cs

services.AddAuthentication(options =>
            {
               var tokenValidationParameters = new TokenValidationParameters
               {
                   ValidateIssuer = false,
                   ValidateAudience = false,
                   ValidateIssuerSigningKey = false
               };

                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })//.AddCustomAuthenticationBearer();

                .AddJwtBearer(options =>
            {
                options.SecurityTokenValidators.Clear();
                options.SecurityTokenValidators.Add(new CustomJwtSecurityTokenHandler(_configuration));
                options.TokenValidationParameters = tokenValidationParameters;
                options.SaveToken = false;
                options.Events = new JwtBearerEvents
                {
                    OnTokenValidated = context =>
                    {
                        context.Success();
                        return Task.CompletedTask;
                    }
                };
            });

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //else
            //{
            //    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            //    app.UseHsts();
            //}

            //app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseMvc();

            app.UseSwagger();
            app.UseSwaggerUI(options =>
            {
                foreach (var description in provider.ApiVersionDescriptions)
                {
                    options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
                }
            });
        }

CustomJwtSecurityTokenHandler

public class CustomJwtSecurityTokenHandler : ISecurityTokenValidator
    {
        public bool CanValidateToken => true;
        public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;

        private readonly JwtSecurityTokenHandler _tokenHandler;
        private readonly string _fcAuthUrl;

        public CustomJwtSecurityTokenHandler(IConfiguration configuration)
        {
            _tokenHandler = new JwtSecurityTokenHandler();
            _fcAuthUrl = configuration["Authentication:BaseUri"];
        }

        public bool CanReadToken(string securityToken)
        {
            return _tokenHandler.CanReadToken(securityToken);
        }

        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
            out SecurityToken validatedToken)
        {
            var jwt = _tokenHandler.ReadJwtToken(securityToken);
            var accessToken = jwt.Claims.FirstOrDefault(c => c.Type == "auth_token")?.Value;

            if (accessToken == null)
            {
                validatedToken = null;
                return null;
            }

            var task = IsTokenValid(accessToken);
            task.Wait();
            if (task.Result)
            {
                validatedToken = new JsonWebToken(securityToken);
                return new ClaimsPrincipal();
            }

            validatedToken = null;
            return null;
        }

        private async Task<bool> IsTokenValid(string accessToken)
        {
            // My validation here
            // Simple http call to authentication service to validate token
        }
    }

Here is the logs I have on one call :

2020-02-21 16:12:58.194 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Hosting.Internal.WebHost] [Information] Request starting HTTP/1.1 GET https://localhost:5001/api/notifications/translations

2020-02-21 16:12:58.458 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler] [Information] Successfully validated the token.

2020-02-21 16:12:58.485 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Routing.EndpointMiddleware] [Information] Executing endpoint '"NotificationCenter.Api.Controllers.TranslationController.GetStandardTranslations (NotificationCenter.API)"'

2020-02-21 16:12:58.522 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker] [Information] Route matched with "{action = \"GetStandardTranslations\", controller = \"Translation\"}". Executing controller action with signature "System.Threading.Tasks.Task`1[NotificationCenter.BusinessLogic.DTOs.Responses.TranslationsResponseDTO] GetStandardTranslations()" on controller "NotificationCenter.Api.Controllers.TranslationController" ("NotificationCenter.API").

2020-02-21 16:12:58.538 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Authorization.DefaultAuthorizationService] [Information] Authorization failed.

2020-02-21 16:12:58.540 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker] [Information] Authorization failed for the request at filter '"Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter"'.

2020-02-21 16:12:58.547 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.ChallengeResult] [Information] Executing ChallengeResult with authentication schemes ([]).

2020-02-21 16:12:58.557 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler] [Information] AuthenticationScheme: "Bearer" was challenged.

2020-02-21 16:12:58.562 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker] [Information] Executed action "NotificationCenter.Api.Controllers.TranslationController.GetStandardTranslations (NotificationCenter.API)" in 32.8311ms

2020-02-21 16:12:58.585 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Routing.EndpointMiddleware] [Information] Executed endpoint '"NotificationCenter.Api.Controllers.TranslationController.GetStandardTranslations (NotificationCenter.API)"'

2020-02-21 16:12:58.628 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Hosting.Internal.WebHost] [Information] Request finished in 433.6413ms 401

而不是new JsonWebToken尝试使用 _tokenHandler 验证它,如下所示:

validatedToken = _tokenHandler.ReadJwtToken(securityToken);
//Startup.cs
...
 var tokenValidationParameters = JwtHelper.GetValidationParameters();

            services
               .AddAuthentication(o =>
               {
                   o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                   o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
               })
               .AddJwtBearer(config =>
               {
                   var serviceProvider = services.BuildServiceProvider();
                   config.SecurityTokenValidators.Clear();
                   config.SecurityTokenValidators.Add(new CustomJwtSecurityTokenHandler(serviceProvider));
                   config.TokenValidationParameters = tokenValidationParameters;
                   config.SaveToken = false;
                   config.Events = new JwtBearerEvents
                   {
                       OnTokenValidated = context =>
                       {
                           context.Success();

                           return Task.CompletedTask;
                       }
                   };
               });

...

//CustomJwtSecurityTokenHandler.cs

 public class CustomJwtSecurityTokenHandler : ISecurityTokenValidator
    {
        public bool CanValidateToken => true;
        public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;

        private readonly JwtSecurityTokenHandler _tokenHandler;
        private readonly ServiceProvider _serviceProvider;

        public CustomJwtSecurityTokenHandler(ServiceProvider serviceProvider)
        {
            _tokenHandler = new JwtSecurityTokenHandler();
            _serviceProvider = serviceProvider;
        }

        public bool CanReadToken(string securityToken)
        {
            return _tokenHandler.CanReadToken(securityToken);
        }

        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
            out SecurityToken validatedToken)
        {

            var jwthandler = new CustomJwtSecurityTokenHandler();
            IPrincipal principal;
            
            if (JwtHelper.ValidateToken(securityToken, out principal,out validatedToken) == true)
            {

                var jwt = _tokenHandler.ReadJwtToken(securityToken);
                //var userId = jwt.Claims.FirstOrDefault(c => c.Type.Contains("unique_name") == true)?.Value; 
                var claimsIdentity = principal.Identity as ClaimsIdentity;
                var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;
                //var _system = jwt.Claims.FirstOrDefault(c => c.Type.Contains("system") == true)?.Value;
                var _system = claimsIdentity.FindFirst(ClaimTypes.System)?.Value;
                string user;
                string domain;
                if (userId != null && _system != null)
                {
                    
                    user = GlobalData.Instance.DecodeUser(_serviceProvider.GetService<IHttpContextAccessor>()?.HttpContext?.Request?.Headers.FirstOrDefault(h => h.Key == "UserName").Value);
                    domain = GlobalData.Instance.DecodeUser(_serviceProvider.GetService<IHttpContextAccessor>()?.HttpContext?.Request?.Headers.FirstOrDefault(h => h.Key == "Domain").Value);
                    userId = userId.Replace(domain+"\\", "");
                    user = user.Replace("\\\\", "\\");
                    user = user.Replace(domain+"\\", "");

                    if (_system.ToLower() != Environment.MachineName.ToLower())
                        throw new Exception("Invalid token");
                    if (user.ToLower() != userId.ToLower())
                        throw new Exception("Invalid token");
                    //var claimsIdentity = principal.Identity as ClaimsIdentity;
                    return principal as ClaimsPrincipal;
                }      
               
            }
            return new ClaimsPrincipal();
        }
    }

//JwtHelper

public class JwtHelper
    {
        public static IConfiguration AppSetting { get; set; }
        public static bool ValidateToken(string authToken,out  IPrincipal principal,out SecurityToken validatedToken)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var validationParameters = GetValidationParameters();

           
            principal = tokenHandler.ValidateToken(authToken, validationParameters, out validatedToken);

            return true;
        }
            static IConfiguration ConfigurationManager()
            {
              IConfiguration  AppSetting = new ConfigurationBuilder()
                        .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                        .AddJsonFile("appsettings.json")
                        .Build();
            return AppSetting;
            }
        public static TokenValidationParameters GetValidationParameters()
        {
            AppSetting = ConfigurationManager();
            string Issuer = AppSetting["JwtIssuerOptions:Issuer"];
            string Audience = AppSetting["JwtIssuerOptions:Audience"];
            string ValidFor = AppSetting["JwtIssuerOptions:ValidFor"];

            return new TokenValidationParameters()
            { 
                ValidateLifetime = true, 
                ValidateAudience = true,
                ValidateIssuerSigningKey = true,
                ValidateIssuer = true,   
                ValidIssuer = Issuer,
                ValidAudience = Audience,
                RequireExpirationTime = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("__SymmetricSecurityKey__")), // The same key as the one that generate the token
                ClockSkew = TimeSpan.Zero
            };
        }
       
    }

//JwtController.cs
...
 MachineName = Environment.MachineName;
             identity = new ClaimsIdentity(new List<Claim>
                {
                    new Claim(ClaimTypes.Name, credentials.UserName),
                    new Claim(ClaimTypes.System, MachineName),
                }, "Custom");

             tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = identity,
                Issuer = _jwtOptions.Issuer,
                Audience= _jwtOptions.Audience,
                Expires = DateTime.Now + _jwtOptions.ValidFor,
                SigningCredentials = _jwtOptions.SigningCredentials,
            };

             handler = new JwtSecurityTokenHandler();
             token = handler.CreateToken(tokenDescriptor);
             encodedJwtCustom = handler.WriteToken(token);

            // Serialize and return the response
            var response = new
            {
                accessToken = encodedJwtCustom,
                expiresIn = (int)_jwtOptions.ValidFor.TotalSeconds,
                userName = userInfo.SAMAccountName,
                domain,
                displayName = userInfo.FirstName + " " + userInfo.LastName
            };

            return new OkObjectResult(response);
  ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM