Spring Boot 4 / Spring Security 6 – @WithMockUser returns 401 Unauthorized in MockMvc test

2 days ago 1
ARTICLE AD BOX

I’m setting up a project using Spring Boot 4.0.2 (Spring Security 6) and I’m getting a 401 Unauthorized in a MockMvc integration test, even though I’m using @WithMockUser(roles = "ADMIN").

I suspect this might be related to changes in Spring Security packages or test configuration in comparison to Spring Boot 3x, but I can’t spot what’s wrong.

Security configuration

@Configuration public class ProjectConfig { @Bean UserDetailsService userDetailsService() { var user = User.withUsername("john") .password("{noop}password") .roles("USER") .build(); var admin = User.withUsername("admin") .password("{noop}admin") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.POST, "/api/v1/products").hasRole("ADMIN") .anyRequest().authenticated() ) .httpBasic(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable); return http.build(); } }

Controller

@RestController @RequestMapping("/api/v1/products") @RequiredArgsConstructor public class ProductController { private final ProductService productService; @PostMapping public ResponseEntity<Product> createProduct(@RequestBody Product product) { return new ResponseEntity<>( productService.createProduct(product), HttpStatus.CREATED ); } }

Integration test

import com.francislainy.ecomproductservice.TestcontainersConfiguration; import com.francislainy.ecomproductservice.service.impl.ProjectConfig; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc @Import({TestcontainersConfiguration.class, ProjectConfig.class}) public class ProductIT { @Autowired MockMvc mockMvc; @WithMockUser(roles = {"ADMIN"}) @Test void shouldCreateProductWhenAdmin() throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/products") .contentType(MediaType.APPLICATION_JSON) .content("{}")) .andExpect(status().isCreated()); } }

Relevant dependencies

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.0.2</version> </parent> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc-test</artifactId> <scope>test</scope> </dependency>

Mockito response

MockHttpServletRequest: HTTP Method = POST Request URI = /api/v1/products Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"2"] Body = {} Session Attrs = {SPRING_SECURITY_SAVED_REQUEST=DefaultSavedRequest [http://localhost/api/v1/products?continue]} Handler: Type = null Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 401 Error message = Unauthorized Headers = [WWW-Authenticate:"Basic realm="Realm"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = null Body = Forwarded URL = null Redirected URL = null Cookies = [] java.lang.AssertionError: Status expected:<201> but was:<401> Expected :201 Actual :401

Thanks very much.

Read Entire Article