移除WebSecurityConfigurerAdapter后控制器测试未加载安全过滤器解决方法
问题描述
在我的Spring Boot应用中,我按照官方博客的推荐方案,移除安全配置中已废弃的WebSecurityConfigurerAdapter类的使用。
此前我的安全配置类实现如下:
@EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 过滤器类构造参数均为SecurityConfiguration中通过@Autowired注入的字段 var jwtAuthenticationFilter = new JwtAuthenticationFilter( authenticationManager(), authenticationAuditService, jwtTokenService, roleResolver, objectMapper, messageSourceAccessor, roleActivityService); var jwtAuthorisationFilter = new JwtAuthorisationFilter(jwtTokenService, userRepository, userRoleRepository); http.cors().and().csrf().disable().authorizeRequests() .anyRequest().authenticated().and() .addFilter(jwtAuthenticationFilter) .addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class) .addFilterBefore(filterChainExceptionHandler, LogoutFilter.class) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
上述配置的核心逻辑是创建2个Spring Security自定义过滤器并注册到过滤器链中。移除WebSecurityConfigurerAdapter后,安全配置类修改为如下形式:
@EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true) public class SecurityConfiguration { @Bean public SecurityFilterChain configure(HttpSecurity http) throws Exception { // 过滤器类构造参数均为SecurityConfiguration中通过@Autowired注入的字段 var jwtAuthenticationFilter = new JwtAuthenticationFilter( authenticationConfiguration.getAuthenticationManager(), authenticationAuditService, jwtTokenService, roleResolver, objectMapper, messageSourceAccessor, roleActivityService); var jwtAuthorisationFilter = new JwtAuthorisationFilter(jwtTokenService, userRepository, userRoleRepository); http.cors().and().csrf().disable().authorizeRequests() .anyRequest().authenticated().and() .addFilter(jwtAuthenticationFilter) .addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class) .addFilterBefore(filterChainExceptionHandler, LogoutFilter.class) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); return http.build(); } }
但完成上述修改后大量测试用例执行失败,排查发现故障原因是安全过滤器不再被加载。例如下方测试用例在配置修改前可正常通过:
@WebMvcTest(MyController.class) @WithMockUser public class MyControllerTests { @Autowired private MyController controller; @Test void deleteRole_ShouldThrowsAccessDeniedException_WhenUserHasInvalidRole() { assertThrows(AccessDeniedException.class, () -> this.controller.deleteRole(UUID.randomUUID())); } }
被测试的控制器方法定义如下:
@PreAuthorize("hasAuthority('SYSTEM_ADMIN')") public void deleteRole(@PathVariable UUID id) { // 实现逻辑省略 }
当前测试失败的原因是,实现@PreAuthorize注解定义的角色校验逻辑所需的安全过滤器未被加载。请问如何在运行控制器测试时强制加载自定义安全配置,使安全过滤器正常生效?
解决方案
故障核心原因是@WebMvcTest属于Web层切片测试,默认不会自动扫描加载你自定义的SecurityConfiguration类。旧版继承WebSecurityConfigurerAdapter的写法有Spring Boot测试模块的特殊适配逻辑,切换为新版组件化SecurityFilterChain配置后,适配逻辑不再生效,需要手动显式导入配置:
- 在测试类上添加
@Import(SecurityConfiguration.class)注解,将自定义安全配置类显式导入测试上下文,让Spring能识别你定义的过滤器链Bean。 @WebMvcTest只会加载Web层相关Bean,你自定义过滤器依赖的AuthenticationConfiguration、JWT服务、持久层Repository、业务Service这类非Web层组件不会被自动注入,需要为这些依赖添加@MockBean注解提供模拟实现,避免测试上下文启动失败。- 如果希望所有
@WebMvcTest测试默认加载安全配置,可以在测试资源目录下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,写入安全配置类的全限定名,实现全局自动导入。
修正后的测试类示例:
@WebMvcTest(MyController.class) @Import(SecurityConfiguration.class) // 显式导入自定义安全配置 @WithMockUser public class MyControllerTests { @Autowired private MyController controller; // 按需Mock安全配置依赖的非Web层Bean @MockBean private AuthenticationAuditService authenticationAuditService; @MockBean private JwtTokenService jwtTokenService; @MockBean private RoleResolver roleResolver; @MockBean private ObjectMapper objectMapper; @MockBean private MessageSourceAccessor messageSourceAccessor; @MockBean private RoleActivityService roleActivityService; @MockBean private UserRepository userRepository; @MockBean private UserRoleRepository userRoleRepository; @MockBean private FilterChainExceptionHandler filterChainExceptionHandler; @Test void deleteRole_ShouldThrowsAccessDeniedException_WhenUserHasInvalidRole() { assertThrows(AccessDeniedException.class, () -> this.controller.deleteRole(UUID.randomUUID())); } }
额外优化建议:可以将两个自定义JWT过滤器直接声明为Spring Bean,通过方法参数注入到SecurityFilterChain的定义方法中,避免手动new实例时遗漏依赖、注入失败的问题。
内容的提问来源于stack exchange,提问作者Antonio Dragos
相关产品推荐
相关产品推荐

