Reputation: 1871
I am using reactive Spring Boot 3.4.3 with webflux. For easier setup, CSRF has been disabled .csrf(ServerHttpSecurity.CsrfSpec::disable)
. Then I turn it on in this way (because of expected integration with JavaScript frontend)
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
My integration tests start failing, which is expected.
403 FORBIDDEN Forbidden
An expected CSRF token cannot be found
So I added .mutateWith(csrf())
as suggested in the documentation https://docs.spring.io/spring-security/reference/reactive/test/web/csrf.html
It leads to
java.lang.NullPointerException: Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filters(java.util.function.Consumer)" because "httpHandlerBuilder" is null
at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$CsrfMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:260)
That is a known error for non-reactive code, see csrf() doesn't work with WebTestClient in non-reactive code But as I stated before, my project is reactive.
The simplified test looks something like this
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class UserControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@WithUserDetails("john.doe")
void testDeleteUserAsOrganizationAdmin() {
webTestClient
.mutateWith(csrf())
.post()
// rest omitted for brevity
}
So question is:
How to make Spring webflux integration test working with CSRF?
Upvotes: 0
Views: 51
Reputation: 11319
Update
The issue is that the OP is missing the @AutoConfigureWebTestClient
annotation.
If you are looking to load your full application configuration and use WebTestClient, you should consider @SpringBootTest combined with @AutoConfigureWebTestClient rather than this annotation.
Example Hello World test:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class MyControllerTest {
@Autowired
WebTestClient webTestClient;
@Test
void postWithoutCsrf_expectForbidden() {
webTestClient
.post()
.uri("/hello")
.exchange()
.expectStatus().isForbidden();
}
@Test
void postWithCsrf_expectOk() {
webTestClient
.mutateWith(csrf())
.post()
.uri("/hello")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
Original answer
A lightweight alternative for testing specific controllers, which is sufficient to address this issue, is to use @WebFluxTest
with mocked dependencies. The SecurityWebFilterChain
can be configured within a @TestConfiguration
or imported using @Import(FluxSecurityConfig.class)
(use the name for your config class).
When commenting out .mutateWith(csrf())
, you will get a 403 FORBIDDEN
.
@WebFluxTest(UserController.class)
class UserControllerTest {
@Autowired
WebTestClient webTestClient;
// use your actual service(s) here
@MockitoBean
MyService myService;
@TestConfiguration
@EnableWebFluxSecurity
static class TestConfiguration {
@Bean
public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
return http
.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
.authorizeExchange(exchanges -> exchanges.anyExchange().permitAll())
.build();
}
}
@Test
void testDeleteUserAsOrganizationAdmin() {
// setup mocks
webTestClient
.mutateWith(csrf())
.post()
// remaining code left out
// verify mocks
}
// other tests here
}
Upvotes: 1