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

Spring Filter Chain测试报错:预期200 OK实际返回401未授权

自定义JWT过滤器导致401未授权错误排查与解决

问题背景

实现了继承OncePerRequestFilter的自定义过滤器JwtTokenAuthenticationFilter,用于验证入站请求的令牌头,无效时抛出UnauthorizedException。测试控制器时收到如下错误:

java.lang.AssertionError: Status expected:<200 OK> but was:<401 UNAUTHORIZED>
 at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)

排查原因

1. 父类Mock配置未生效

AbstractControllerTest中的setUp方法配置了iamTokenService的Mock逻辑,但MainControllerTest的setup方法未调用父类的setUp(),导致令牌验证的Mock规则没有初始化,iamTokenService.validateToken可能返回默认的无效结果,触发401。

2. 过滤器逻辑存在重复验证错误

在JwtTokenAuthenticationFilter的doFilterInternal方法中,重复调用了服务令牌的验证:

if (validateToken(request, serviceTokenHeaderName)
        && validateToken(request, serviceTokenHeaderName)) {

此处第二个验证应该针对认证令牌头authTokenHeaderName,重复验证服务令牌会导致认证令牌未被校验,后续逻辑可能因缺少认证令牌信息抛出异常。

3. Mock过滤器未正确跳过验证逻辑

MainControllerTest中虽然Mock了JwtTokenAuthenticationFilter,但未明确配置其doFilterInternal方法的行为,实际过滤器仍会执行真实的验证逻辑,导致401。

4. processAuthToken直接解析JWT绕过Mock服务

processAuthToken方法直接调用SignedJWT.parse(authToken)解析令牌,未使用Mock的iamTokenService获取用户信息。测试中传入的真实JWT可能存在payload字段不符合预期的情况,触发UnauthorizedException。

解决方案

1. 确保父类Mock配置生效

在MainControllerTest的setup方法中调用父类的setUp(),初始化令牌验证的Mock规则:

@BeforeEach
public void setup() {
    try {
        super.setUp();
    } catch (IAMClientException | ServerErrorException e) {
        log.error("Failed to initialize mock config", e);
    }
    this.client = WebTestClient
            .bindToApplicationContext(this.context)
            .configureClient()
            .build();
}

2. 修正过滤器的重复验证逻辑

修改JwtTokenAuthenticationFilter的doFilterInternal方法,分别验证服务令牌和认证令牌:

@Override
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    if (validateToken(request, serviceTokenHeaderName)
            && validateToken(request, authTokenHeaderName)) {
        if (!processAuthToken(request)) {
            logger.warn(getServletContext(), new Error("Could not read data from auth token ".concat(authTokenHeaderName)));
            throw new UnauthorizedException(new ErrorInfo("", "Could not read data from auth token ".concat(authTokenHeaderName), Map.of()));
        }
    }

    filterChain.doFilter(request, response);
}

3. 配置Mock过滤器跳过验证

在测试类中,让Mock的过滤器直接执行过滤器链,不做验证:

@BeforeEach
public void setup() {
    try {
        super.setUp();
    } catch (IAMClientException | ServerErrorException e) {
        log.error("Failed to initialize mock config", e);
    }
    
    // 让Mock过滤器直接放行
    Mockito.doAnswer(invocation -> {
        FilterChain chain = invocation.getArgument(2);
        chain.doFilter(invocation.getArgument(0), invocation.getArgument(1));
        return null;
    }).when(filter).doFilterInternal(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class));
    
    this.client = WebTestClient
            .bindToApplicationContext(this.context)
            .configureClient()
            .build();
}

4. 让processAuthToken依赖Mock服务(可选)

修改processAuthToken方法,通过iamTokenService获取用户信息,避免直接解析JWT:

// 先在IAMTokenService中添加获取用户信息的方法
// TokenUserInfo getTokenUserInfo(String token);

private boolean processAuthToken(HttpServletRequest request) {
    String authToken = request.getHeader(authTokenHeaderName);
    try {
        TokenUserInfo userInfo = iamTokenService.getTokenUserInfo(authToken);
        String principalId = userInfo.getPrincipalId();
        String principalKind = userInfo.getPrincipalKind();
        String support = userInfo.getSupport();
        
        if (StringUtils.hasText(principalId)
                && "IAM_USER".equals(principalKind)
                && "true".equals(support)) {
            this.principalId = principalId;
        } else {
            throw new Exception("Invalid token user info");
        }
    } catch (Exception e) {
        logger.error(getServletContext(), e);
        throw new UnauthorizedException(new ErrorInfo("", "Unable to Verify ".concat(authTokenHeaderName), Map.of()));
    }
    return true;
}

// 在父类setUp中添加Mock规则
when(this.iamTokenService.getTokenUserInfo(VALID_TOKEN)).thenReturn(new TokenUserInfo("createdByPrincipalId", "IAM_USER", "true"));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 18:35:27