0

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...

  • Addition:`CsrfFilter.equalsConstantTime()` gets expected="4567ba8a-65a7-4366-90bc-ea30dd560d61", actual="Ii6AujkmK2h2QeR7kcHHIjyLvN1SzbK4G6KNgzBSeLyr5tDoFhu2jVtHEwlbd9EapuzzEQq9keRir9GVfsO-s1Q2TYqbgubZ" in my example, this is shown in the screenshot. – Jan Kohnert Dec 08 '22 at 11:44

0 Answers0