.NET Core中如何无异常优雅处理过期JWT令牌?
.NET Core JWT过期令牌异常原因及优雅处理方案
我在.NET Core中开发JWT令牌的生成与验证功能,使用的令牌生成代码如下:
string GenerateToken() { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(mySecret)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new Claim[] { new(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()), }; var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor() { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddSeconds(10), SigningCredentials = credentials, Audience = "audience", Issuer = "issuer" }; var securityToken = tokenHandler.CreateToken(tokenDescriptor); var token = tokenHandler.WriteToken(securityToken); return token; }
令牌验证代码:
void ValidateToken(string token) { JwtSecurityTokenHandler validationHandler = new(); var claimsPrincipal = validationHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "issuer", ValidAudience = "audience", IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(mySecret)), ClockSkew = TimeSpan.Zero }, out SecurityToken securityToken); }
当令牌过期时,会抛出如下异常:
IDX10223: Lifetime validation failed. The token is expired. ValidTo
(UTC): '6/9/2024 6:21:36 AM', Current time (UTC): '6/9/2024 6:21:38
AM'.
该问题在控制台应用和使用Microsoft.AspNetCore.Authentication.JwtBearer包的Web API中都会出现,Web API会返回401状态码。
一、异常抛出原因
- 生命周期验证强制开启:验证代码中设置了
ValidateLifetime = true,会严格检查令牌的有效期,当令牌的ValidTo时间早于当前UTC时间时,直接触发生命周期验证失败。 - 无时钟偏差容忍:配置了
ClockSkew = TimeSpan.Zero,默认JWT验证会允许几分钟的时钟偏差(避免服务器时间不一致导致的误判),但这里关闭了该容忍机制,令牌过期哪怕一秒都会立即抛出异常。 - Web API中间件默认行为:使用
JwtBearer中间件时,框架会自动执行令牌验证流程,验证失败(包括过期)会直接抛出异常,并返回401未授权状态码。
二、优雅处理过期令牌的方案
1. 控制台应用(手动验证场景)
通过try-catch精准捕获SecurityTokenExpiredException,针对性处理过期情况,避免异常扩散:
void ValidateToken(string token) { JwtSecurityTokenHandler validationHandler = new(); try { var claimsPrincipal = validationHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "issuer", ValidAudience = "audience", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(mySecret)), ClockSkew = TimeSpan.Zero }, out SecurityToken securityToken); // 验证成功后的业务逻辑 } catch (SecurityTokenExpiredException) { // 令牌过期专属处理逻辑 Console.WriteLine("令牌已过期,请重新获取"); } catch (Exception ex) { // 处理其他验证失败场景(如签名无效、受众/发行者不匹配等) Console.WriteLine($"令牌验证失败:{ex.Message}"); } }
2. Web API场景(使用JwtBearer中间件)
方案一:通过JwtBearer事件自定义过期响应
在配置JwtBearer中间件时,注册OnAuthenticationFailed事件,捕获过期异常并返回友好格式化响应:
// 在Program.cs中配置认证服务 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "issuer", ValidAudience = "audience", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(mySecret)), ClockSkew = TimeSpan.Zero }; options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { if (context.Exception is SecurityTokenExpiredException) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json"; var response = new { Message = "令牌已过期,请重新登录" }; return context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(response)); } // 其他验证异常处理 context.Response.StatusCode = StatusCodes.Status401Unauthorized; return context.Response.WriteAsync("令牌验证失败"); } }; });
方案二:自定义全局异常过滤器
创建全局异常过滤器,统一捕获SecurityTokenExpiredException并返回标准化响应:
public class JwtExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext context) { if (context.Exception is SecurityTokenExpiredException) { context.Result = new JsonResult(new { StatusCode = StatusCodes.Status401Unauthorized, Message = "令牌已过期,请重新获取新令牌" }) { StatusCode = StatusCodes.Status401Unauthorized }; context.ExceptionHandled = true; } } } // 在Program.cs中注册过滤器 builder.Services.AddControllers(options => { options.Filters.Add<JwtExceptionFilter>(); });
内容的提问来源于stack exchange,提问作者Md. Mustafizur Rahman
相关产品推荐
相关产品推荐

