自定义ASP.NET JWT认证:自行校验Token并解决c.Token为空问题
自定义ASP.NET JWT认证与授权流程(自行校验Token)
需求概述
- 自定义实现ASP.NET应用的JWT认证与授权(包含角色功能)
- 不依赖ASP.NET内置的Token校验逻辑,完全自行实现Token有效性校验
- 校验完成后告知ASP.NET认证是否成功,并注入角色等Claim信息
期望步骤
- 实现认证与授权流程
- 从请求中获取JWT Token
- 自行编写Token有效性校验逻辑
- 根据校验结果通知ASP.NET认证状态,并构建包含角色的ClaimsPrincipal
已尝试方案(存在问题)
用户尝试使用AddJwtBearer并通过OnMessageReceived事件自定义逻辑,但c.Token始终为空:
builder.AddJwtBearer(o => { // JWT Validation o.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = false, ValidateIssuer = true, ValidIssuers = new[] { "http://localhost:8080/realms/GT" }, ValidateIssuerSigningKey = true, IssuerSigningKey = BuildRSAKey(publicKeyJWT), ValidateLifetime = true }; o.Events = new JwtBearerEvents() { OnMessageReceived = async c => { if (c.Token == null) { c.NoResult(); c.Response.StatusCode = 500; c.Response.ContentType = "text/plain"; await c.Response.WriteAsync("No token was provided"); return; } // custom logic to check token // if true then Success // else fail BuildClaims(c.Principal); // roles claims included return; } }; });
问题点
OnMessageReceived事件中c.Token为空,无法获取到请求中的JWT Token进行自定义校验。
解决方案
方案一:修正OnMessageReceived事件,手动提取Token
OnMessageReceived中c.Token为空是因为默认逻辑还未提取Token,你可以手动从请求头(或其他自定义位置)提取Token:
builder.AddJwtBearer(o => { // 禁用所有内置Token校验,避免与自定义逻辑冲突 o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateIssuerSigningKey = false, ValidateLifetime = false }; o.Events = new JwtBearerEvents() { OnMessageReceived = async c => { // 从Authorization头提取Bearer Token var authHeader = c.Request.Headers.Authorization.FirstOrDefault(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) { c.NoResult(); c.Response.StatusCode = StatusCodes.Status401Unauthorized; await c.Response.WriteAsync("Invalid or missing authorization header"); return; } // 提取Token核心部分 var token = authHeader.Substring("Bearer ".Length).Trim(); if (string.IsNullOrEmpty(token)) { c.NoResult(); c.Response.StatusCode = StatusCodes.Status401Unauthorized; await c.Response.WriteAsync("No token was provided"); return; } // 执行自定义Token校验逻辑 bool isTokenValid = ValidateTokenCustomLogic(token); if (!isTokenValid) { c.NoResult(); c.Response.StatusCode = StatusCodes.Status401Unauthorized; await c.Response.WriteAsync("Invalid token"); return; } // 构建包含角色的自定义ClaimsPrincipal var principal = BuildCustomClaimsPrincipal(token); c.Principal = principal; c.Success(); // 通知ASP.NET认证成功 } }; }); // 自定义校验方法示例 private bool ValidateTokenCustomLogic(string token) { // 在这里实现你的Token校验逻辑:比如签名验证、有效期检查、Issuer校验等 // 示例:解析Token并校验Issuer var handler = new JwtSecurityTokenHandler(); var jwtToken = handler.ReadJwtToken(token); return jwtToken.Issuer == "http://localhost:8080/realms/GT" && jwtToken.ValidTo > DateTime.UtcNow; } // 构建ClaimsPrincipal示例(包含角色) private ClaimsPrincipal BuildCustomClaimsPrincipal(string token) { var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token); var claims = jwtToken.Claims.ToList(); // 手动添加角色Claim(如果Token中没有的话) claims.Add(new Claim(ClaimTypes.Role, "Admin")); var identity = new ClaimsIdentity(claims, "CustomJwt"); return new ClaimsPrincipal(identity); }
方案二:完全自定义认证Handler(更灵活)
如果需要彻底脱离JwtBearer中间件的默认逻辑,可以实现IAuthenticationHandler接口,完全掌控认证流程:
- 创建自定义认证Handler:
public class CustomJwtAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> { public CustomJwtAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { // 1. 从请求中提取Token var authHeader = Request.Headers.Authorization.FirstOrDefault(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) { return AuthenticateResult.Fail("Missing or invalid authorization header"); } var token = authHeader.Substring("Bearer ".Length).Trim(); // 2. 执行自定义Token校验逻辑 bool isValid = await ValidateTokenAsync(token); if (!isValid) { return AuthenticateResult.Fail("Invalid token"); } // 3. 构建包含角色的ClaimsPrincipal var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token); var claims = jwtToken.Claims.ToList(); // 添加自定义角色Claim(按需调整) claims.Add(new Claim(ClaimTypes.Role, "Admin")); var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); // 4. 返回认证成功结果 return AuthenticateResult.Success(ticket); } private Task<bool> ValidateTokenAsync(string token) { // 在这里实现完整的自定义校验逻辑:签名、有效期、Issuer/Audience等 try { var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token); return Task.FromResult( jwtToken.Issuer == "http://localhost:8080/realms/GT" && jwtToken.ValidTo > DateTime.UtcNow && // 其他校验规则... true); } catch { return Task.FromResult(false); } } }
- 注册自定义认证方案:
builder.Services.AddAuthentication("CustomJwt") .AddScheme<AuthenticationSchemeOptions, CustomJwtAuthenticationHandler>("CustomJwt", options => { }); // 配置授权策略(比如基于角色的授权) builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); });
- 启用认证和授权中间件:
app.UseAuthentication(); app.UseAuthorization();
这种方式完全独立于ASP.NET内置的JWT校验逻辑,适合复杂的自定义认证需求。
内容的提问来源于stack exchange,提问作者Franco
相关产品推荐
相关产品推荐

