[英]Custom JWT token validation in .NET Core 2.2
我正在嘗試使用自定義驗證來驗證 JWT 令牌。 令牌是在我自己的 API 之外的另一個 API 中生成的,但我有一種方法可以針對服務驗證它。
我不知道缺少什么......即使我的驗證是正確的,我也會不斷收到 401 代碼。
編輯:添加了令牌驗證參數並增加了有關令牌驗證的精度
到目前為止,這是我的代碼:
啟動.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
}
}
這是我在一次通話中的日志:
2020-02-21 16:12:58.194 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Hosting.Internal.WebHost] [信息] 請求開始 HTTP/1.1 GET https://localhost:5001/api /通知/翻譯
2020-02-21 16:12:58.458 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler] [信息] 成功驗證了令牌。
2020-02-21 16:12:58.485 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Routing.EndpointMiddleware] [信息] 執行端點 '"NotificationCenter.Api.Controllers.TranslationController.GetStandardTranslations (NotificationCenter.API )"'
2020-02-21 16:12:58.522 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker] [信息] 路由與“{action = \”GetStandardTranslations\”匹配,控制器 = \“翻譯\”}”。 在控制器“NotificationCenter.Api.Controllers.TranslationController”(“NotificationCenter.API”)上執行帶有簽名“System.Threading.Tasks.Task`1[NotificationCenter.BusinessLogic.DTOs.Responses.TranslationsResponseDTO] GetStandardTranslations()”的控制器操作。
2020-02-21 16:12:58.538 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Authorization.DefaultAuthorizationService] [信息] 授權失敗。
2020-02-21 16:12:58.540 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker] [信息] 過濾器“Microsoft.AspNetCore.Mvc. Authorization.AuthorizeFilter"'。
2020-02-21 16:12:58.547 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.ChallengeResult] [信息] 使用身份驗證方案 ([]) 執行 ChallengeResult。
2020-02-21 16:12:58.557 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler] [信息] AuthenticationScheme:“承載”受到挑戰。
2020-02-21 16:12:58.562 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker] [信息] 執行操作“NotificationCenter.Api.Controllers.TranslationController.GetStandardTranslations (NotificationCenter. API)" 在 32.8311 毫秒內
2020-02-21 16:12:58.585 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Routing.EndpointMiddleware] [信息] 執行的端點'"NotificationCenter.Api.Controllers.TranslationController.GetStandardTranslations (NotificationCenter.API )"'
2020-02-21 16:12:58.628 +01:00 [0HLTMS23C48IQ:00000001] [] [Microsoft.AspNetCore.Hosting.Internal.WebHost] [信息] 請求在 433.6413 毫秒內完成 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);
...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.