Describe the bug mockJwt() WebTestClientConfigurer does not seem to work with MockMvcWebTestClient that was introduced in Spring 5.3 as documented.

To Reproduce

    @Test
    void getMessagesWebTestClient() {
        final WebTestClient testClient = MockMvcWebTestClient.bindTo(this.mockMvc)
                .build();
        testClient.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read")))
                .get()
                .uri("/messages")
                .exchange()
                .expectStatus().isOk()
                .expectBody()
                .jsonPath("$[0]").isEqualTo("hello")
                .jsonPath("$[1]").isEqualTo("world"); ;
    }

throws the exception below

java.lang.NullPointerException: Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filter(org.springframework.web.server.WebFilter[])" because "httpHandlerBuilder" is null

    at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$JwtMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:540)
    at org.springframework.test.web.reactive.server.DefaultWebTestClientBuilder.apply(DefaultWebTestClientBuilder.java:247)
    at org.springframework.test.web.reactive.server.DefaultWebTestClient.mutateWith(DefaultWebTestClient.java:160)
    at com.example.demojwttest.MessageControllerTest.getMessagesWebTestClient(MessageControllerTest.java:51)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)

while the equivalent following tests work

// MockMvc
    @Test
    void getMessages() throws Exception {
        this.mockMvc.perform(get("/messages")
                .with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read"))))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0]").value("hello"))
                .andExpect(jsonPath("$[1]").value("world"));
    }

// @WithMockUser
    @Test
    @WithMockUser(authorities = "SCOPE_message:read")
    void getMessagesWebTestClientWithMockUser() {
        final WebTestClient testClient = MockMvcWebTestClient.bindTo(this.mockMvc)
                .build();
        testClient.get()
                .uri("/messages")
                .exchange()
                .expectStatus().isOk()
                .expectBody()
                .jsonPath("$[0]").isEqualTo("hello")
                .jsonPath("$[1]").isEqualTo("world"); ;
    }

Expected behavior

The first example works

Sample https://github.com/making/spring-security-gh-9257

Comment From: jzheaux

While there may be a wider picture to consider, the basic reason for the NPE is because MockMvcWebTestClient.bindTo ultimately wires WebTestClient with a ClientHttpConnector instead of a WebHttpHandlerAdapter. Spring Security uses this adapter in order to introduce WebFilters into the mock client.

Comment From: rwinch

UPDATED: Fixed workaround, demo CSRF support workaround, and provide link to complete example.

@making Thanks for the report. We will look into a proper solution.

In the meantime, you can work around it using TestSecurityContextHolder.setAuthentication(Authentication). I put together a complete example in the gh-9257-webtestclient branch of my sample repository. You can see an excerpt below :

@SpringBootTest
@AutoConfigureMockMvc
public class WebTestClientTest {
    WebTestClient client;

    @MockBean
    // mock the JwtDecoder so that the jwks is not resolved since no AuthZ Server Setup
    JwtDecoder jwtDecoder;

    // Override the CsrfTokenRepository. Must explicitly wire CsrfTokenRepository Bean into DSL for this to work
    @MockBean
    CsrfTokenRepository csrfTokenRepository;

    DefaultCsrfToken csrf = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "123");

    @Autowired
    void setMockMvc(MockMvc mockMvc) {
        this.client = MockMvcWebTestClient.bindTo(mockMvc)
                .build();
    }

    @BeforeEach
    void setupCsrf() {
        given(this.csrfTokenRepository.generateToken(any())).willReturn(csrf);
    }

    private Consumer<HttpHeaders> csrf() {
        return (headers) -> headers.set(csrf.getHeaderName(), csrf.getToken());
    }

    @Test
    void getWhenAuthenticated() {
        TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());

        client
                .get()
                .uri("/")
                .exchange()
                .expectStatus().isOk();
    }

    @Test
    void getWhenNotAuthenticated() {
        client
                .get()
                .uri("/")
                .exchange()
                .expectStatus().is4xxClientError();
    }

    @Test
    void csrfWhenNoToken() {
        TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());

        client
                .post()
                .uri("/")
                .exchange()
                .expectStatus().is4xxClientError();
    }

    @Test
    void csrfWhenValidToken() {
        TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());

        client
                .post()
                .uri("/")
                .headers(csrf())
                .exchange()
                .expectStatus().isOk();
    }

    private static JwtAuthenticationToken jwtAuthenticationToken() {
        return new JwtAuthenticationToken(jwt().build(), AuthorityUtils.createAuthorityList("SCOPE_message:read"));
    }

    public static Jwt.Builder jwt() {
        // @formatter:off
        return Jwt.withTokenValue("token")
                .header("alg", "none")
                .audience(Arrays.asList("https://audience.example.org"))
                .expiresAt(Instant.MAX)
                .issuedAt(Instant.MIN)
                .issuer("https://issuer.example.org")
                .jti("jti")
                .notBefore(Instant.MIN)
                .subject("mock-test-subject");
        // @formatter:on
    }
}

Comment From: rwinch

@rstoyanchev Any ideas on how we can get Spring Security to integrate with the MockMvcWebTestClient support? With WebTestClient we typically add a WebFilter which can access the attribute on ServerWebExchange and that sets up the context, but it doesn't appear there is a way to do this when using MockMvcWebTestClient.

We need to be able to access the attribute on the ServerWebExchange that is populated by the mutateWith method and use it to setup the SecurityContext.

Comment From: rstoyanchev

I think there is some misunderstanding. As of 5.3 WebTestClient can be used to exercise not only WebFlux but also WebMvc controllers with MockMvc as the server. In other words it's all MockMvc, and unrelated to WebFlux, and therefore any WebFlux related hooks do not apply.

For the most part what can be done directly with MockMvc can also be done via MockMvcWebTestClient. For example, you can configure and apply extension hooks to MockMvc just the same. However since it it a client, it is not as easy to modify individual requests with some special support via server side Filter. I can work with you more closely if we want to find a way to improve that.

For further reference the sections on WebTestClient and MockMvc have been updated to reflect this. You can also see all the framework samples tests for MockMvc ported to use with WebTestClient.

Comment From: gursahibsahni

@making Thanks for the report. We will look into a proper solution. In the meantime, you can work around it using:

java @Test void getMessagesWebTestClient() { TestingAuthenticationToken authentication = new TestingAuthenticationToken("a", "b", "SCOPE_message:read"); TestSecurityContextHolder.setAuthentication(authentication); final WebTestClient testClient = MockMvcWebTestClient.bindTo(this.mockMvc) .build(); testClient.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read"))) .get() .uri("/messages") .exchange() .expectStatus().isOk() .expectBody() .jsonPath("$[0]").isEqualTo("hello") .jsonPath("$[1]").isEqualTo("world"); ; }

This still doesn't work for me.

Comment From: membersound

Similar also happens in a test with @Autowired WebTestClient webTestClient and executing a webTestClient.mutateWith(csrf()).post()...:

java.lang.NullPointerException
    at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$CsrfMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:259)
    at org.springframework.test.web.reactive.server.DefaultWebTestClientBuilder.apply(DefaultWebTestClientBuilder.java:265)
    at org.springframework.test.web.reactive.server.DefaultWebTestClient.mutateWith(DefaultWebTestClient.java:167)

Comment From: soasada

I'm experiencing the same behaviour than @membersound, do you have any workaround?

Comment From: rwinch

@gursahibsahni Sorry the workaround I posted previously, should have removed the mutateWith method. I've updated the example and provided a link to a repository that demonstrates the workaround in its entirety.

@soasada @membersound I've updated the sample above to demonstrate how to workaround CSRF test support not working too.

@rstoyanchev Thanks for the reply. I'd like to figure out a way that Spring Security users can use WebTestClient using the same APIs for WebMvc and WebFlux backends. It is confusing that mutateWith does not work for MockMvc based tests. What's more is there is currently no way to use many of Spring Security's test features when WebTestClient + MockMvc.

Comment From: nasrmohammad4804

hi i have same issue with webTestClient and get null pointer exception i think because don't use jwt for webTestClient and i solved this with add @AutoConfigureWebTestClient as class level annotation for integration test 198698140-27aa94c0-631d-4c26-b9d6-9f71cfb758c8

Comment From: eiswind

The suggested workaround does not work with the current spring security versions (6.0.2), as csrf token handling has changed quite a bit. So far I was not able to create a working solution.

Comment From: siaavush

as @eiswind mentioned, I have the same issue with spring boot 3 and security 6 this is my test class:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@AutoConfigureWireMock(stubs = ["classpath:/mappings/authservice"], port = 0)
@TestPropertySource(
    properties = [
        "spring.flyway.enabled=false",
    ],
)
@Testcontainers
@ExtendWith(SpringExtension::class)
@ActiveProfiles("test")
@AutoConfigureWebTestClient
class ServiceIntegrationTest {
@Test
    fun test() {
           webTestClient.mutateWith(mockJwt().jwt(JwtUtils.generateJwt()))
            .mutateWith(csrf())
            .post()
            .uri("/api/planning/stores/$businessUnitId/units")
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isForbidden
     }
}

and this is the error I am getting:

Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filter(org.springframework.web.server.WebFilter[])" because "httpHandlerBuilder" is null
java.lang.NullPointerException: Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filter(org.springframework.web.server.WebFilter[])" because "httpHandlerBuilder" is null
    at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$JwtMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:540)

none of the workarounds seem effective on it

Comment From: justin-tay

It's currently not possible to do this cleanly with MockMvc and using the mutateWith api because the functionality is in MockMvcHttpConnector and the WebTestClient.Builder does not expose a method to set a modified MockMvcHttpConnector as the ClientHttpConnector is final in the builder.

The best workaround currently if you still want to use WebTestClient instead of just using the MockMvc API is https://github.com/spring-projects/spring-security/issues/9304#issuecomment-841495717.

Some ideas * The WebTestClient.Builder needs to expose a method to set the ClientHttpConnector or some ClientHttpConnectorBuilder * The MockMvcHttpConnector needs to store the RequestPostProcessors to be applied on the MockHttpServletRequestBuilder and a means to create a new MockMvcHttpConnector with the existing RequestPostProcessors and additional ones * The MockMvcHttpConnector needs to apply the RequestPostProcessors in adaptRequest.

Comment From: justin-tay

As a workaround I wrote a simple builder which can be used with the org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.

Edit: Have edited the builder as when I was doing testing I realised that what I typically wanted to use was the filters customised by Spring Boot in the MockMvcBuilder and not binding to the WebApplicationContext with springSecurity.

package com.example;

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import org.springframework.http.HttpMethod;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder;
import org.springframework.web.context.WebApplicationContext;

public class DefaultMockMvcWebTestClient implements WebTestClient {
    private final Function<MockHttpServletRequestBuilder, WebTestClient> builder;
    private final List<RequestPostProcessor> requestPostProcessors = new ArrayList<>();

    public DefaultMockMvcWebTestClient(WebApplicationContext context) {
        this.builder = (requestBuilder) -> MockMvcWebTestClient.bindToApplicationContext(context)
                .apply(springSecurity()).defaultRequest(requestBuilder);
    }

    public DefaultMockMvcWebTestClient(AbstractMockMvcBuilder<?> mockMvcBuilder) {
        this.builder = (requestBuilder) -> MockMvcWebTestClient
                .bindTo(mockMvcBuilder.defaultRequest(requestBuilder).build()).build();
    }

    public DefaultMockMvcWebTestClient(Function<MockHttpServletRequestBuilder, WebTestClient> builder) {
        this.builder = builder;
    }

    public DefaultMockMvcWebTestClient(DefaultMockMvcWebTestClient copy) {
        this.builder = copy.builder;
        this.requestPostProcessors.addAll(copy.requestPostProcessors);
    }

    public DefaultMockMvcWebTestClient with(RequestPostProcessor requestPostProcessor) {
        this.requestPostProcessors.add(requestPostProcessor);
        return this;
    }

    public DefaultMockMvcWebTestClient mutateWith(RequestPostProcessor requestPostProcessor) {
        DefaultMockMvcWebTestClient copy = new DefaultMockMvcWebTestClient(this);
        return copy.with(requestPostProcessor);
    }

    public WebTestClient build() {
        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/");
        requestPostProcessors.stream().forEach(requestBuilder::with);
        return this.builder.apply(requestBuilder);
    }

    @Override
    public RequestHeadersUriSpec<?> get() {
        return build().get();
    }

    @Override
    public RequestHeadersUriSpec<?> head() {
        return build().head();
    }

    @Override
    public RequestBodyUriSpec post() {
        return build().post();
    }

    @Override
    public RequestBodyUriSpec put() {
        return build().put();
    }

    @Override
    public RequestBodyUriSpec patch() {
        return build().patch();
    }

    @Override
    public RequestHeadersUriSpec<?> delete() {
        return build().delete();
    }

    @Override
    public RequestHeadersUriSpec<?> options() {
        return build().options();
    }

    @Override
    public RequestBodyUriSpec method(HttpMethod method) {
        return build().method(method);
    }

    @Override
    public Builder mutate() {
        return build().mutate();
    }

    @Override
    public WebTestClient mutateWith(WebTestClientConfigurer configurer) {
        return build().mutateWith(configurer);
    }
}

It can be used something like

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;

public class MyTest {
    DefaultMockMvcWebTestClient webTestClient;

    @Autowired
    public void setupClient(AbstractMockMvcBuilder<?> mockMvcBuilder) {
        this.webTestClient = new DefaultMockMvcWebTestClient(mockMvcBuilder);
    }

    @Test
    public void test() {
        this.webTestClient.mutateWith(oidcLogin()).get() // ...
    }
    // ...
}

Comment From: rstoyanchev

@rwinch as per my https://github.com/spring-projects/spring-framework/pull/30233#pullrequestreview-1637375454, I think we could do something with the WebTestClient#mutate(WebTestClientConfigurer) hook. For WebFlux use, I believe Spring Security is using the WebHttpHandlerBuilder argument to modify the server. For WebMvc use, it would use the ClientHttpConnector argument which would be the MockMvcClientHttpConnector. We'll need to make some changes in Spring Framework to allow MockMvcClientHttpConnector to be mutated and that should make it possible for Spring Security to address this issue.

Comment From: IsaacHu

Any target date for the final solution of this issue? We also suffer in this issue when using WebTestClient to test Spring MVC after involved security.

@WebMvcTest(controllers = XxxController.class)
@WithMockUser
class XxxControllerTest {

    @Autowired
    private MockMvc mockMvc;

    private WebTestClient webTestClient;

    @BeforeEach
    void setupWebClient() {
        webTestClient = MockMvcWebTestClient
                .bindTo(mockMvc)
                .build()
// will encounter NPE if -> .mutateWith(csrf())
        ;
    }

Comment From: glorfidev

hi i have same issue with webTestClient and get null pointer exception i think because don't use jwt for webTestClient and i solved this with add @AutoConfigureWebTestClient as class level annotation for integration test 198698140-27aa94c0-631d-4c26-b9d6-9f71cfb758c8

Could you provide a source code? I think that @AutoConfigureWebTestClient should not have an impact on the configuration of webtestclient if SpringBootTest is used with RANDOM_PORT

Comment From: Bourg

We just worked our way through this same rabbit hole at my job and came up with a solution that works in the latest versions.

For us, the key pieces were:

  1. Configure your own WebTestClient in the way described below - do not rely on the WebTestClient that is auto-configured with @SpringBootTest(webEnvironment = RANDOM_PORT) or similar
  2. Use the Spring Security test context annotations to mock the security context - do not try to use .mutateWith on the WebTestClient

Set your WebTestClient up like this in your test class:

    private WebTestClient webTestClient;

    @Autowired
    public void setWebApplicationContext(final WebApplicationContext context) {
        webTestClient = MockMvcWebTestClient
                .bindToApplicationContext(context)
                .apply(SecurityMockMvcConfigurers.springSecurity())
                .build();
    }

this portion of the solution was found in https://github.com/spring-projects/spring-security/issues/9304#issuecomment-841495717

Then, you can annotate your test classes / methods with @WithMockUser or any custom annotation created with @MockSecurityContext + a corresponding implementation of WithSecurityContextFactory (see https://docs.spring.io/spring-security/reference/servlet/test/method.html#test-method-withsecuritycontext)

Full working test class calling a controller that echoes the principal's name:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebTestClientMockUserTest {
    private WebTestClient webTestClient;

    @Autowired
    public void setWebApplicationContext(final WebApplicationContext context) {
        webTestClient = MockMvcWebTestClient
                .bindToApplicationContext(context)
                .apply(SecurityMockMvcConfigurers.springSecurity())
                .build();
    }

    @Test
    @WithMockUser("Austin")
    void testWithMockUser() {
        webTestClient
                .get()
                .uri("/whoami")
                .exchange()
                .expectStatus()
                .isOk()
                .expectBody(String.class)
                .isEqualTo("You are Austin");
    }
}

I think the thing that is causing everyone the most trouble is that you need to mock the security in a WebMVC-first way, not the WebFlux-first way exposed on WebTestClient.mutateWith(), which as all the above comments point out is only designed to operate on the reactive security stack.

Comment From: javolek

Since version 6.1 there seems to be a possibility to use RequestPostProcessor of MockMvc for a MockMvcHttpConnector inside WebTestClientConfigurer - see: https://github.com/spring-projects/spring-framework/issues/31298. So it is possible to solve it the following way (kotlin):

    fun WebTestClient.mutateWith(rpp: RequestPostProcessor) =
        this.mutateWith { builder, _, connector ->
            (connector as? MockMvcHttpConnector)?.let { mockMvcConnector ->
                builder.clientConnector(mockMvcConnector.with(listOf(rpp)))
            }
    }

This is simple kotlin extension function to WebTestClient allowing passing RequestPostProcessor instace (i.e. SecurityMockMvcRequestPostProcessors.jwt) to mutateWith method. In Java one would need to implement WebTestClientConfigurer. Is there a simpler way to do it?