TokenValidationMiddleware测试失败:无效Token未返回401请求排查
JWT Token验证中间件问题修复方案
一、中间件实现修复
先排查核心逻辑错误,常见问题包括验证失效后未终止请求流程、日志未正确注入,修复后代码如下:
修复后的中间件代码
public class TokenValidationMiddleware { private readonly RequestDelegate _next; private readonly ILogger<TokenValidationMiddleware> _logger; private readonly IConfiguration _configuration; public TokenValidationMiddleware(RequestDelegate next, ILogger<TokenValidationMiddleware> logger, IConfiguration configuration) { _next = next; _logger = logger; _configuration = configuration; } public async Task InvokeAsync(HttpContext context) { // 检查Authorization请求头 var authHeader = context.Request.Headers.Authorization.FirstOrDefault(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) { _logger.LogWarning("缺失或无效的Authorization请求头"); context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsync("未授权:缺少有效Token"); return; // 终止请求流程,不再执行后续中间件 } // 提取Token var token = authHeader.Substring("Bearer ".Length).Trim(); try { // 配置JWT验证参数 var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = _configuration["Jwt:Issuer"], ValidAudience = _configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"])) }; // 执行验证,Token失效会抛出SecurityTokenExpiredException tokenHandler.ValidateToken(token, validationParameters, out _); // 验证通过,继续执行后续中间件 await _next(context); } catch (SecurityTokenExpiredException ex) { _logger.LogError(ex, "Token已过期"); context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsync("未授权:Token已过期"); return; // 终止请求流程 } catch (Exception ex) { _logger.LogError(ex, "Token验证失败"); context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsync("未授权:无效Token"); return; // 终止请求流程 } } }
关键修复点
- 所有验证失败场景(无Token、Token过期、验证异常)都调用
return终止请求,避免后续中间件返回200 - 注入
ILogger实现日志输出,解决测试无日志问题 - 针对Token过期异常单独捕获,精准返回错误信息
二、单元测试代码修正
以xUnit + Moq为例,修正测试逻辑,确保正确模拟场景并验证结果:
修复后的测试代码
public class TokenValidationMiddlewareTests { private readonly Mock<ILogger<TokenValidationMiddleware>> _mockLogger; private readonly IConfiguration _configuration; public TokenValidationMiddlewareTests() { _mockLogger = new Mock<ILogger<TokenValidationMiddleware>>(); // 模拟JWT配置项 var configDict = new Dictionary<string, string> { {"Jwt:Issuer", "TestIssuer"}, {"Jwt:Audience", "TestAudience"}, {"Jwt:SecretKey", "TestSecretKey1234567890123456"} }; _configuration = new ConfigurationBuilder() .AddInMemoryCollection(configDict) .Build(); } [Fact] public async Task InvokeAsync_ExpiredToken_Returns401() { // 生成过期的JWT Token var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"]); var tokenDescriptor = new SecurityTokenDescriptor { Issuer = _configuration["Jwt:Issuer"], Audience = _configuration["Jwt:Audience"], Expires = DateTime.UtcNow.AddHours(-1), // 设置为已过期 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var expiredToken = tokenHandler.WriteToken(token); // 模拟HttpContext var context = new DefaultHttpContext(); context.Request.Headers.Authorization = $"Bearer {expiredToken}"; var responseBody = new MemoryStream(); context.Response.Body = responseBody; // 模拟后续中间件(即使返回200,也不应被执行) RequestDelegate next = (ctx) => { ctx.Response.StatusCode = StatusCodes.Status200OK; return Task.CompletedTask; }; var middleware = new TokenValidationMiddleware(next, _mockLogger.Object, _configuration); // 执行中间件 await middleware.InvokeAsync(context); // 断言响应结果 context.Response.Body.Seek(0, SeekOrigin.Begin); var responseContent = await new StreamReader(context.Response.Body).ReadToEndAsync(); Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); Assert.Contains("Token已过期", responseContent); // 验证日志是否正确输出 _mockLogger.Verify( l => l.Log( LogLevel.Error, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => v.ToString().Contains("Token已过期")), It.IsAny<SecurityTokenExpiredException>(), It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once); } }
关键修复点
- 主动生成过期测试Token,确保测试场景符合预期
- 模拟Response.Body读取响应内容,验证错误信息
- 通过Moq验证日志是否正确输出,确认中间件日志逻辑正常
- 模拟后续中间件返回200,验证失效场景下请求流程被正确终止
三、启动配置验证
确保Program.cs中中间件注册顺序正确(必须在业务中间件之前):
var builder = WebApplication.CreateBuilder(args); // 注册JWT配置(需确保appsettings.json中有对应配置项) builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"])) }; }); // 注册自定义中间件 builder.Services.AddTransient<TokenValidationMiddleware>(); var app = builder.Build(); // 中间件顺序:自定义Token验证必须在业务路由之前 app.UseMiddleware<TokenValidationMiddleware>(); // 其他中间件 app.UseAuthorization(); app.MapControllers(); app.Run();
注意事项
- 如果使用官方JWT认证中间件,可直接用
UseAuthentication()和UseAuthorization()替代自定义中间件 - 自定义中间件必须放在
MapControllers等业务路由中间件之前,确保请求先经过Token验证
四、验证步骤
- 运行单元测试,确认过期Token场景返回401,且日志输出正常
- 启动应用,用Postman测试:
- 不带Token请求:返回401,日志有警告
- 带过期Token请求:返回401,日志有错误
- 带有效Token请求:返回200(业务接口正常响应)
内容的提问来源于stack exchange,提问作者Yardi
相关产品推荐
相关产品推荐

