JwtBearer中间件与UseAuthorization冲突,需支持过期JWT令牌登录
需求
希望已过期的JWT令牌仍能用于登录,为此编写了JwtExpirationMiddleware自定义中间件,但在Startup配置时遇到中间件顺序冲突问题。
现有代码
JwtExpirationMiddleware 实现代码
using System; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using Hoopt.Application.Infrastructure.Auth; using Hoopt.Helper; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; public class JwtExpirationMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; public JwtExpirationMiddleware(RequestDelegate next, IConfiguration configuration) { _next = next; _configuration = configuration; } public async Task Invoke(HttpContext context) { // 获取请求头中的Token var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(", ").Last().Replace("Bearer ", ""); if (token != null) { try { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_configuration["JwtConfig:Secret"].Sha256()); var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, // 验证JWT签名 IssuerSigningKey = new SymmetricSecurityKey(key), // 设置加密密钥 ValidateIssuer = false, ValidateAudience = false, RequireExpirationTime = false, ValidateLifetime = false }; var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken); context.User = principal; } catch (Exception ex) { if (ex.Message.StartsWith("IDX10224")) { context.Response.StatusCode = (int)HttpStatusCode.Accepted; } else { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return; } } } await _next(context); } }
Startup.cs 原配置代码
app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseMiddleware<JwtExpirationMiddleware>(Configuration); app.UseSentryTracing(); app.UseCors("Default"); app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/"); endpoints.MapControllers(); });
遇到的冲突问题
- 保留
UseAuthorization中间件时,请求会直接返回401,自定义中间件根本没有执行的机会 - 移除
UseAuthorization后,系统抛出强制错误:
Endpoint Hoopt.Api.Controllers.UserController.GetUserProfile (***) contains authorization metadata, but a middleware was not found that supports authorization.
Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...)
解决方案
问题核心是中间件顺序错误:ASP.NET Core中间件按注册顺序执行,原配置中UseAuthorization在自定义中间件之前,导致过期Token被授权中间件直接拦截。
步骤1:调整中间件执行顺序
将自定义JwtExpirationMiddleware移到UseAuthentication之后、UseAuthorization之前,确保自定义中间件先处理Token,再由授权中间件进行后续校验:
修改后的Startup.cs配置:
app.UseRouting(); // 先执行系统认证中间件 app.UseAuthentication(); // 优先处理自定义JWT过期逻辑,再执行系统授权中间件 app.UseMiddleware<JwtExpirationMiddleware>(Configuration); app.UseAuthorization(); app.UseSentryTracing(); app.UseCors("Default"); app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/"); endpoints.MapControllers(); });
步骤2:优化自定义中间件的异常处理逻辑
原代码捕获IDX10224(Token过期)错误时,仅设置状态码但未正确传递用户身份,需要补充解析Token获取用户信息的逻辑:
修改Invoke方法中的异常捕获块:
catch (Exception ex) { if (ex.Message.StartsWith("IDX10224")) { // Token过期但签名有效,直接解析Token获取用户身份 var jwtToken = tokenHandler.ReadJwtToken(token); var identity = new ClaimsIdentity(jwtToken.Claims, "jwt"); context.User = new ClaimsPrincipal(identity); // 设置状态码并继续执行后续中间件 context.Response.StatusCode = (int)HttpStatusCode.Accepted; } else { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return; } }
逻辑说明
- 调整顺序后,自定义中间件先完成Token解析(跳过过期校验)并设置用户身份,后续
UseAuthorization会基于已有的用户信息进行权限判断,不会直接拦截请求 - 异常处理优化后,即使Token过期,只要签名有效就能获取用户身份,满足过期Token用于登录的需求
内容的提问来源于stack exchange,提问作者İbrahim Halil

