Spring Filter Chain测试报错:预期200 OK实际返回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

