skip to content

SecurityMockMvc & WebTestClient Mutators

Request post-processors like user(), csrf() and jwt() — and their WebTestClient mutators — attach authentication and CSRF tokens to test requests. Interviewers ask because forgetting csrf() is why half of all first Spring Security tests fail.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

4

In a Spring MockMvc test, how do you make a request run as an authenticated user, and why does a POST often need `.with(csrf())`?

level: juniorimportance: must knowfreq 70%

answer

  1. SecurityMockMvcRequestPostProcessors static import
  2. .with(user(...)) attaches a per-request SecurityContext
  3. roles() adds ROLE_ prefix, authorities() verbatim
  4. csrf() needed for POST/PUT/DELETE when CSRF enabled
  5. csrf().asHeader() / useInvalidToken()

basics

~10 s

Use SecurityMockMvcRequestPostProcessors: mockMvc.perform(get("/").with(user("alice").roles("ADMIN"))) runs the request as that user. State-changing requests (POST/PUT/DELETE) need .with(csrf()) because CSRF protection rejects them without a valid token.

solid answer

~40 s

Spring Security ships `SecurityMockMvcRequestPostProcessors` (static-import it). `user("alice").roles("ADMIN")` builds a `SecurityContext` holding a `UsernamePasswordAuthenticationToken` and attaches it to the request, so the filter chain sees an authenticated principal for that single request — you attach it with `MockHttpServletRequestBuilder.with(...)`. `csrf()` injects a valid CSRF token where `CsrfFilter` expects it. When CSRF protection is enabled (the default for stateful/form login), any mutating method (POST, PUT, PATCH, DELETE) is rejected with 403 unless a valid token is present, so tests add `.with(csrf())`. You can chain them: `.with(user("alice")).with(csrf())`. For negative tests, `csrf().useInvalidToken()` forces a failure. This all requires the security filter chain to be wired into MockMvc — automatic under `@SpringBootTest` + `@AutoConfigureMockMvc`.

code

java · 26 lines
java
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class AdminControllerTest {

    @Autowired MockMvc mockMvc;

    @Test
    void adminCanCreate() throws Exception {
        mockMvc.perform(post("/api/widgets")
                        .with(user("alice").roles("ADMIN"))
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"name\":\"w1\"}"))
               .andExpect(status().isCreated());
    }

    @Test
    void missingCsrfIsForbidden() throws Exception {
        mockMvc.perform(post("/api/widgets").with(user("alice").roles("ADMIN")))
               .andExpect(status().isForbidden());
    }
}

go deeper

for a junior

Know the two everyday post processors: user() for auth, csrf() for mutating requests, and that you attach them with .with().

for a middle

Explain the 403-vs-401 distinction and when csrf() is unnecessary (stateless bearer-token APIs).

for a senior

Discuss the wiring requirement (springSecurity() configurer) and roles/authorities exclusivity.

for a principal

Frame per-request post processors vs annotation-based static context and how CSRF policy in the SecurityFilterChain drives test needs.

## What these are `SecurityMockMvcRequestPostProcessors` is a utility class in `spring-security-test` (package `org.springframework.security.test.web.servlet.request`). It provides static factory methods that return `RequestPostProcessor` objects — small hooks that mutate a `MockHttpServletRequest` just before MockMvc dispatches it. You attach one with the `with(...)` method on a request builder: ```java import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; mockMvc.perform(get("/admin").with(user("alice").roles("ADMIN"))) .andExpect(status().isOk()); ``` ## `user(...)` `user("alice")` creates a lightweight authenticated context for just this request. Under the hood it builds a `UsernamePasswordAuthenticationToken` and puts it into a `SecurityContext` that the security filter chain reads. Modifiers: - `.roles("ADMIN", "USER")` — each becomes an authority prefixed with `ROLE_` (so `ROLE_ADMIN`). - `.authorities("SCOPE_read")` — sets authorities verbatim, no prefix. (`roles` and `authorities` are mutually exclusive — the last one you call wins.) - `.password(...)`, `.name(...)` — rarely needed for authorization tests. There is also an overload `user(UserDetails)` when you already have a domain user. ## `csrf()` Spring Security's `CsrfFilter` protects state-changing HTTP methods (anything that is not GET/HEAD/OPTIONS/TRACE) by requiring a CSRF token. In a real browser the token arrives from a prior page; in a MockMvc test there is no prior page, so the request has no token and `CsrfFilter` returns **403 Forbidden** via `AccessDeniedHandler`. `csrf()` supplies a token that matches what the configured `CsrfTokenRepository` expects, so the request passes. Variants: - `csrf()` — places the token as a request parameter (`_csrf`). - `csrf().asHeader()` — sends it as the `X-CSRF-TOKEN` header instead (useful for JSON/AJAX-style endpoints). - `csrf().useInvalidToken()` — deliberately sends a bad token so you can assert the 403 path. ## When csrf() is NOT needed If CSRF protection is disabled — common for **stateless** APIs secured by a bearer token (OAuth2 Resource Server), where you'd typically configure `http.csrf(csrf -> csrf.disable())` — the filter is absent and adding `.with(csrf())` is harmless but unnecessary. GET requests never need it. ## Wiring requirement These post processors only work if the Spring Security filter chain is part of the MockMvc instance. With `@SpringBootTest` + `@AutoConfigureMockMvc` it is auto-applied. If you build MockMvc manually (`MockMvcBuilders.webAppContextSetup(context)`), you must add `.apply(SecurityMockMvcConfigurers.springSecurity())`, otherwise the filters never run and `user()`/`csrf()` silently do nothing. ## Chaining Each `with(...)` adds one post processor; call it multiple times to combine: `.with(user("alice").roles("ADMIN")).with(csrf())`.

  • What is the difference between `.roles("ADMIN")` and `.authorities("ROLE_ADMIN")` on `user()`?
    `roles("ADMIN")` automatically prepends `ROLE_`, giving authority `ROLE_ADMIN`. `authorities("ROLE_ADMIN")` sets it verbatim. They are equivalent here, but `roles` and `authorities` are mutually exclusive — calling both means the last one wins.
  • Your POST test returns 403 even though you added `.with(user(...))`. What's the likely cause?
    CSRF protection is enabled and the request has no token — add `.with(csrf())`. (403, not 401, is the tell: the user IS authenticated but the CSRF check fails.)

saying these in an interview costs you the question

  • Thinking `.with(user(...))` alone lets a POST through when CSRF is enabled
  • Believing csrf() logs a user in (it only supplies a token; user() handles auth)
  • Assuming roles("ROLE_ADMIN") works — it double-prefixes to ROLE_ROLE_ADMIN

context

open as a page

How do the `jwt()` and `oauth2Login()` request post processors differ, and when would you reach for each in a MockMvc test?

level: middleimportance: should knowfreq 55%

basics

~20 s

jwt() simulates an OAuth2 Resource Server request — it injects a fake JwtAuthenticationToken so you don't need a real token or decoder. oauth2Login() simulates a browser login via an OAuth2/OIDC provider, injecting an OAuth2AuthenticationToken with an OAuth2User. Use jwt() for stateless APIs, oauth2Login() for login-based apps.

open as a page

How do you test a secured reactive (WebFlux) endpoint with `WebTestClient`, and how do the `SecurityMockServerConfigurers` mutators map to the servlet post processors?

level: seniorimportance: should knowfreq 45%

basics

~10 s

For WebFlux, use SecurityMockServerConfigurers with WebTestClient. Apply springSecurity() when binding the client, then per request call .mutateWith(mockUser()), .mutateWith(csrf()), .mutateWith(mockJwt()), or .mutateWith(mockOAuth2Login()). These are the reactive equivalents of the servlet user()/csrf()/jwt()/oauth2Login() post processors.

open as a page

Mechanically, how does `user()` (and friends) get a `SecurityContext` in front of the filter chain, and how does that interact with `@WithMockUser` and the `springSecurity()` configurer?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

The post processor stores a SecurityContext on the request (via a test SecurityContextRepository) before the filters run. The security filter chain — added by springSecurity() — reads it and populates SecurityContextHolder for that request. @WithMockUser sets the context statically for the whole method; a per-request post processor overrides it.

open as a page