We got the problem described here and used the solution linked there and provided by Spring.
The solution fixes our end-to-end using WebClient and a filter function, but unfortunately introduces Problems with our REST-controller unit tests, that stop working.
The relevant cecurity configuration using the documented answer:
// Enable CSRF security
http.csrf { csrfConfigurer ->
// see https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_angularjs_or_another_javascript_framework
val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
val delegate = XorCsrfTokenRequestAttributeHandler()
// set the name of the attribute the CsrfToken will be populated on
delegate.setCsrfRequestAttributeName("_csrf")
// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
val requestHandler = CsrfTokenRequestHandler(delegate::handle)
csrfConfigurer.csrfTokenRepository(tokenRepository)
csrfConfigurer.csrfTokenRequestHandler(requestHandler)
}
This configuration fixes our end-to-end-tests using a WebClient with the FilterFunction:
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> =
next.exchange(request)
.flatMap { response: ClientResponse ->
if (response.statusCode().is4xxClientError) {
val csrfCookie = response.cookies().getFirst("XSRF-TOKEN")
if (csrfCookie != null) {
val retryRequest: ClientRequest = ClientRequest.from(request)
.headers { httpHeaders -> httpHeaders.set("X-XSRF-TOKEN", csrfCookie.value) }
.cookies { cookies -> cookies.add("XSRF-TOKEN", csrfCookie.value) }
.build()
return@flatMap next.exchange(retryRequest)
}
}
Mono.just(response)
}
The failing tests look like this:
@Test
fun `create tender with copyFrom null should succeed and return 201 and the uuid`() {
mockMvc
.perform(
post("/api/my/endpoint")
.param("title", "Angebot 1")
.param("copyFrom", null)
.with(user(tendererTestUsers[0]))
.with(csrf())
)
.andExpectAll(
status().isCreated,
content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON),
jsonPath("$", `is`(notNullValue()))
)
Debugging leads to a mismatching CSRF-Token provided as shown in this screenshot. Most likely we need to update the test's csrf configuration; anyone any clue on how to do that?
Using the pre-Spring-Boot-3 CSRF security configuration makes the mock-Tests work again, but then the end-to-end-tests fail...