You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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验证

四、验证步骤

  1. 运行单元测试,确认过期Token场景返回401,且日志输出正常
  2. 启动应用,用Postman测试:
    • 不带Token请求:返回401,日志有警告
    • 带过期Token请求:返回401,日志有错误
    • 带有效Token请求:返回200(业务接口正常响应)

内容的提问来源于stack exchange,提问作者Yardi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:52:07