Spring Boot集成测试MockMvc访问JWT安全端点返回401问题
问题根因
你当前的服务是基于JWT的无状态认证架构,所有受保护请求必须先经过自定义的JwtAuthenticationTokenFilter校验请求头中的合法JWT令牌,才会将认证信息写入Spring Security上下文。你通过.with(authentication(...))或者@MockUser提前构造的测试身份,会被前置的JWT过滤器直接覆盖/清空——JWT过滤器的默认逻辑是:只要请求未携带合法JWT,就不会读取Security上下文中预置的认证对象,直接判定为未认证,返回401。
另外你还有一处权限配置的隐性坑:配置中使用的.hasRole("API_ADMIN")方法,Spring Security会自动为权限字符串拼接ROLE_前缀做匹配,你手动构造的权限标识是API_ADMIN,即使认证通过也会因权限标识不匹配返回403。
解决方案
方案1:测试请求携带真实生成的合法JWT(最贴合生产逻辑,推荐)
该方案不需要修改生产环境安全配置,完全模拟真实请求链路:
- 在测试类中注入项目中使用的JWT生成工具,测试执行前生成绑定
API_ADMIN权限的有效JWT - 发起请求时将JWT放入
Authorization请求头,格式遵循Bearer {token}规范
示例代码片段:
@Autowired private JwtTokenUtil jwtTokenUtil; // 替换为你项目中实际的JWT工具类 @Test void getAllTransactionsSuccessTest() throws Exception { String adminToken = jwtTokenUtil.generateToken("admin_user", Collections.singletonList("API_ADMIN")); this.mockMvc.perform(get("/transactions/account?account_id=1") .header("Authorization", "Bearer " + adminToken)) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.totalElements").value(50)) .andExpect(jsonPath("$.transactions").exists()); }
注意:如果你的JwtAuthenticationProvider包含用户存在性校验,需要通过@Sql注解提前插入测试用户数据,或者Mock用户查询逻辑,避免因用户不存在导致认证失败。
方案2:测试环境调整安全配置,跳过JWT强制校验
如果不需要在集成测试中覆盖JWT校验逻辑,可以单独为测试环境编写优先级更高的安全配置,跳过自定义JWT过滤器,直接使用MockMvc传入的认证信息:
- 在test目录下创建测试专用安全配置,覆盖生产配置:
@Configuration @Order(1) public class TestSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .cors() .and() .authorizeRequests() .antMatchers("/transactions/all").hasRole("API_ADMIN") .antMatchers("/auth/token", "/v2/api-docs", "/swagger-resources/**", "/swagger-ui.html", "/webjars/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 测试环境不注册JWT认证过滤器,避免覆盖Mock传入的认证信息 } }
- 修正测试代码中构造认证对象的权限前缀,匹配
hasRole()的匹配规则:
@Test void getAllTransactionsSuccessTest() throws Exception { this.mockMvc.perform(get("/transactions/account?account_id=1") .with(authentication(new UsernamePasswordAuthenticationToken( "admin_user", null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_API_ADMIN")) )))) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.totalElements").value(50)) .andExpect(jsonPath("$.transactions").exists()); }
额外排错点
- 你当前同时使用了
@AutoConfigureMockMvc自动注入MockMvc,又在@BeforeEach中手动通过MockMvcBuilders重复构建MockMvc实例,重复构建可能导致过滤器链加载异常。使用@AutoConfigureMockMvc时直接注入自动生成的MockMvc即可,不需要手动重复构建。 - 检查
JwtAuthenticationTokenFilter的实现逻辑:如果过滤器在请求未携带JWT时主动执行SecurityContextHolder.getContext().setAuthentication(null)清空上下文,所有提前预置的Mock认证信息都会被清除,这也是@MockUser注解不生效的核心原因。
内容的提问来源于stack exchange,提问作者martin kimani
相关产品推荐
相关产品推荐

