Issue
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?
Solution
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
Answered By - Henning
Answer Checked By - Marie Seifert (JavaFixing Admin)