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())`?
answer
- SecurityMockMvcRequestPostProcessors static import
- .with(user(...)) attaches a per-request SecurityContext
- roles() adds ROLE_ prefix, authorities() verbatim
- csrf() needed for POST/PUT/DELETE when CSRF enabled
- csrf().asHeader() / useInvalidToken()
basics
~10 sUse 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 sSpring 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 linesimport 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
Know the two everyday post processors: user() for auth, csrf() for mutating requests, and that you attach them with .with().
Explain the 403-vs-401 distinction and when csrf() is unnecessary (stateless bearer-token APIs).
Discuss the wiring requirement (springSecurity() configurer) and roles/authorities exclusivity.
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