Home > Mobile >  include security filters when running controller tests
include security filters when running controller tests

Time:06-13

In my Spring Boot app, I'm trying to remove the usage of the deprecated class WebSecurityConfigurerAdapter for configuring security, as recommended in this blog post.

Previously, my security configuration class looked like this:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // the filter classes constructor arguments are 
        // @Autowired fields of SecurityConfiguration      
        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();
    }
}

All this really does is create 2 Spring Security filters and adds them to the filter chain. Following the removal of WebSecurityConfigurerAdapter, the class now looks like this:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        // the filter classes constructor arguments are 
        // @Autowired fields of SecurityConfiguration    
        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();
    }
}

However, following these changes a bunch of tests are failing. It seems that the reason for this is because the security filters are no longer loaded. For example, the following test passed before making the changes above

@WebMvcTest(MyController.class)
@WithMockUser
public class MyControllerTests {

    @Autowired
    private MyController controller;

    @Test
    void deleteRole_ShouldThrowsAccessDeniedException_WhenUserHasInvalidRole() {
        assertThrows(AccessDeniedException.class, () -> this.controller.deleteRole(UUID.randomUUID()));
    }
}

The controller method/action that is being tested is:

@PreAuthorize("hasAuthority('SYSTEM_ADMIN')")
public void deleteRole(@PathVariable UUID id) {
  // implementation omitted
}

It appears the test is now failing because the security filters required to implement the role-checking defined by @PreAuthorize are not present.

Is there a way to force the security configuration to be loaded when the tests are run?

CodePudding user response:

You need to annotate your test with

@Import(SecurityConfiguration.class)

The filter for the context slice of @WebMvcTest automatically includes implementations of WebSecurityConfigurerAdapter that are annotated with @Component but it doesn't scan @Bean factory methods of configuration classes.

This is also noted in the release notes for Spring Boot 2.7. There are more details on the discussion here: https://github.com/spring-projects/spring-boot/issues/31162

  • Related