skip to content

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%

answer

  1. post processor stores SecurityContext on request via test SecurityContextRepository
  2. springSecurity() configurer inserts the filter chain — mandatory
  3. SecurityContextHolderFilter loads it into ThreadLocal at request time
  4. @WithMockUser = TestExecutionListener + TestSecurityContextHolder (static, whole method)
  5. per-request post processor overrides annotation; reactive uses Reactor Context

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.

solid answer

~40 s

`SecurityMockMvcRequestPostProcessors` builds a `SecurityContext` (holding a chosen `Authentication`) and saves it into a request attribute using a test-scoped `SecurityContextRepository` — the shared support code is `SecurityContextRequestPostProcessorSupport`. For this to reach the app, the security filter chain must be wired into MockMvc via `SecurityMockMvcConfigurers.springSecurity()` (automatic under `@AutoConfigureMockMvc`). At request time the `SecurityContextHolderFilter`/`SecurityContextPersistenceFilter` loads that stored context into `SecurityContextHolder` (ThreadLocal), so `@PreAuthorize`, `authorizeHttpRequests`, and controller `Principal` all see the fake user. `@WithMockUser`/`@WithSecurityContext` work differently: a `TestExecutionListener` (`WithSecurityContextTestExecutionListener`) sets `TestSecurityContextHolder` before the test method — the `TestSecurityContextRepository` then feeds it into the same filter. Post processors run later, per request, so they override the annotation. Reactive mirrors this via `ReactiveSecurityContextHolder`.

code

java · 18 lines
java
// Hand-built MockMvc MUST apply springSecurity() or nothing authenticates
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(SecurityMockMvcConfigurers.springSecurity())
        .build();

// Static default for the whole method...
@Test
@WithMockUser(username = "default", roles = "USER")
void overrideDemo() throws Exception {
    // ...but this single request overrides it dynamically:
    mockMvc.perform(get("/admin").with(user("alice").roles("ADMIN")))
           .andExpect(status().isOk());

    // this one uses the @WithMockUser 'default' USER and is forbidden
    mockMvc.perform(get("/admin"))
           .andExpect(status().isForbidden());
}

go deeper

for a junior

Just know post processors make the request run as a fake user and springSecurity() must be present.

for a middle

Explain that the context is stored on the request and loaded by the filter chain, not set directly on the holder.

for a senior

Articulate the annotation (TestExecutionListener) vs post-processor (per-request) paths and their precedence.

for a principal

Reason about the fidelity/isolation tradeoffs, the shared test SecurityContextRepository, silent no-op failure modes, and the reactive ReactiveSecurityContextHolder analogue.

## The problem being solved Authorization checks (`@PreAuthorize`, URL rules, `SecurityContextHolder.getContext().getAuthentication()`) read the current `SecurityContext`. To test them you must place a chosen `Authentication` into that context *for the duration of one request*, without going through a real login. `spring-security-test` offers two entry points — **annotations** (static, whole-method) and **request post processors / mutators** (dynamic, per-request) — that both converge on the same filter-based loading mechanism. ## Post processor path (dynamic) 1. `user("alice")` (or `jwt()`, `oauth2Login()`, `authentication(...)`, `securityContext(...)`) returns a `RequestPostProcessor`. These share `SecurityContextRequestPostProcessorSupport`, which knows how to save a `SecurityContext` onto the `MockHttpServletRequest`. 2. When `mockMvc.perform(...)` runs, the post processor executes and **stores the SecurityContext in a request attribute** via a test `SecurityContextRepository` (rather than an HTTP session). 3. The **security filter chain** must be present. `SecurityMockMvcConfigurers.springSecurity()` inserts a `springSecurityFilterChain` delegating filter into MockMvc. `@SpringBootTest` + `@AutoConfigureMockMvc` applies it automatically; a hand-built `webAppContextSetup` must `.apply(springSecurity())`. 4. During the request, `SecurityContextHolderFilter` (Spring Security 6+; formerly `SecurityContextPersistenceFilter`) asks the configured `SecurityContextRepository` for a context. In tests the repository is swapped so it returns the one the post processor stored, and the filter sets it on the ThreadLocal `SecurityContextHolder`. 5. Downstream, authorization filters and method security see an authenticated principal; the controller can inject `Principal`/`Authentication`. 6. After the request the ThreadLocal is cleared, so requests don't leak state. ## Annotation path (static) `@WithMockUser`, `@WithUserDetails`, `@WithSecurityContext`, `@WithAnonymousUser` are handled by `WithSecurityContextTestExecutionListener`, a Spring `TestExecutionListener`. Before the test method it creates the `SecurityContext` and puts it in `TestSecurityContextHolder`. The `TestSecurityContextRepository` (installed by `springSecurity()`) then surfaces that context to the same `SecurityContextHolderFilter` during any request in the method. So the annotation covers **every** request the test makes, statically. ## Interaction / precedence Both funnel through the test `SecurityContextRepository`. When a request also carries a post processor like `.with(user(...))`, the per-request context stored by the post processor **overrides** the annotation's context for that request — dynamic beats static. This lets you set a class/method default with `@WithMockUser` and override individual requests with `.with(...)`. ## `testSecurityContext()` There's an explicit post processor `testSecurityContext()` that bridges a `TestSecurityContextHolder`-populated context into a request when you're building requests in a context where the automatic wiring isn't present — rarely needed with modern auto-config but useful to know it exists. ## Reactive analogue In WebFlux there is no ThreadLocal. `SecurityMockServerConfigurers.springSecurity()` installs a `WebFilter` that reads the mutation attached by `mockUser()`/`mockJwt()`/etc. and writes the `SecurityContext` into the **Reactor `Context`** via `ReactiveSecurityContextHolder`. `@WithMockUser` in reactive tests likewise populates that reactive context. The static-vs-dynamic override semantics carry over. ## Why it matters at a design level - **Fidelity vs. speed:** post processors bypass real authentication (decoders, providers) but exercise the *authorization* half of the chain faithfully — the right tradeoff for testing access rules. - **The wiring requirement is the #1 silent failure:** without `springSecurity()`, both annotations and post processors no-op and everything runs anonymous, which can produce false-green tests (endpoints permit-all) or confusing 401/403s. - **Isolation:** because the context is per-request/per-method and cleared afterward, tests don't bleed authentication into one another — important for parallel/ordered execution.

  • A method annotated `@WithMockUser` also uses `.with(user("alice").roles("ADMIN"))` on one request. Which authentication does that request run as, and why?
    As alice/ADMIN. The per-request post processor stores a SecurityContext that the filter loads, overriding the statically-set @WithMockUser context for that specific request — dynamic beats static.
  • Why can both @WithMockUser and `.with(user(...))` end up doing nothing at all?
    If the security filter chain isn't wired into MockMvc (missing `SecurityMockMvcConfigurers.springSecurity()` in a hand-built client), no filter loads the stored/annotation context, so requests run anonymous.
  • Where does the equivalent context live for a WebFlux test?
    In the Reactor Context via ReactiveSecurityContextHolder; the reactive `springSecurity()` configurer installs a WebFilter that reads the mutator and writes it there, since there's no ThreadLocal.

saying these in an interview costs you the question

  • Claiming the post processor sets SecurityContextHolder directly (it stores it for the filter to load)
  • Saying @WithMockUser and .with(user()) conflict unpredictably — dynamic per-request always wins
  • Believing springSecurity() is optional under all setups (only auto-applied via @AutoConfigureMockMvc/@AutoConfigureWebTestClient)
  • Thinking reactive tests use a ThreadLocal SecurityContextHolder

context