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

移除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 00:09:22