I have configured Spring Security to disable CSRF as follows:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(CsrfConfigurer::disable);
http.cors(CorsConfigurer::disable);
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/actuator/**", "/api/login", "/error")
.permitAll()
.anyRequest()
.permitAll());
http.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
I have a secured API endpoint as follows:
@RestController
class UserRestController {
@PostMapping("/api/users")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<User> create(@RequestBody @Validated User user) {
...
...
return ResponseEntity.status(201).body(savedUser);
}
}
I am able to test this API endpoint using MockMvc as follows:
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureMockMvc
class UserRestControllerTest {
@Autowired
MockMvc mockMvc;
@Test
@WithMockUser(username = "admin@gmail.com")
void shouldCreateUser() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"name": "demo",
"email": "demo@gmail.com",
"password": "demo",
"role": "ROLE_USER"
}
"""))
.andExpect(status().isCreated());
}
}
However, if I use RestAssured instead of MockMvc the test is failing because the response status code is 403.
@SpringBootTest(webEnvironment = RANDOM_PORT)
class UserRestControllerRestAssuredTest {
@LocalServerPort
int port;
@BeforeEach
void setUp() {
RestAssured.port = port;
}
@Test
@WithMockUser(username = "admin@gmail.com")
void shouldCreateUser() {
given()
.contentType(ContentType.JSON)
.body("""
{
"name": "demo",
"email": "demo@gmail.com",
"password": "demo",
"role": "ROLE_USER"
}
""")
.post("/api/users")
.then()
.statusCode(201);
}
}
Expected: As CSRF is disabled in Spring Security configuration, even when I use RestAssured it should work just like MockMvc.
Actual: The test with MockMvc is working fine as expected, but RestAssured test is failing with 403 error.
Sample Reproducer: https://github.com/siva-throwaway-work/spring-security-testing
Comment From: tleipzig
I think MockMvc is doing quite some magic here and using an authenticated user automatically. How does RestAssured, not sharing any session, authenticate itself when doing the call?
Comment From: sivaprasadreddy
I assumed that @WithMockUser would set the Authentication in SecurityContextHolder and the API call will be made from the same thread and it might work. Looks like that's not how it works.
Comment From: sivaprasadreddy
I think the test and RestAssured API call are on different threads. Even setting the Authentication before making API call also not working.
@Test
void shouldCreateUser() {
Authentication authentication = new TestingAuthenticationToken(
"admin@gmail.com", "", List.of(new SimpleGrantedAuthority("ROLE_USER"))
);
SecurityContextHolder.getContext().setAuthentication(authentication);
given()
.contentType(ContentType.JSON)
.body("""
{
"name": "demo",
"email": "demo@gmail.com",
"password": "demo",
"role": "ROLE_USER"
}
""")
.post("/api/users")
.then()
.statusCode(201);
}
It seems the RestAssured API call will be on a different thread, so the only way to test secured endpoints is to performa login, get JWT token and pass it as Authorization header.