How do the `jwt()` and `oauth2Login()` request post processors differ, and when would you reach for each in a MockMvc test?
answer
- jwt() = Resource Server, JwtAuthenticationToken, decoder bypassed
- oauth2Login() = browser login, OAuth2AuthenticationToken + OAuth2User
- jwt() grants NO authorities by default — set them explicitly
- opaqueToken() for introspection, oauth2Client() seeds authorized client
- jwt() usually no csrf(); oauth2Login() mutating still needs csrf()
basics
~20 sjwt() 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.
solid answer
~40 sBoth come from `SecurityMockMvcRequestPostProcessors` and let you test OAuth2 endpoints without a live identity provider. `jwt()` targets a **Resource Server**: it places a `JwtAuthenticationToken` backed by a mock `Jwt` into the context, bypassing the real `JwtDecoder` and signature/issuer checks — customize with `jwt(j -> j.claim("scope", "read").subject("alice"))` and `.authorities(...)`. By default it has no granted authorities unless you set them or rely on your scope→authority converter. `oauth2Login()` targets the **login flow**: it injects an `OAuth2AuthenticationToken` with a default `OAuth2User` and a test `ClientRegistration`, as if the user completed the authorization-code dance — customize the user's attributes/authorities and the registration. There's also `opaqueToken()` for opaque-token resource servers and `oauth2Client()` to seed an authorized client so `@RegisteredOAuth2AuthorizedClient` resolves. Because resource-server APIs are usually stateless, you typically won't need `csrf()` with `jwt()`.
code
java · 14 linesimport static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
// Resource Server: fake JWT with an explicit scope authority
mockMvc.perform(get("/api/orders")
.with(jwt(jwt -> jwt.subject("alice").claim("scope", "orders.read"))
.authorities(new SimpleGrantedAuthority("SCOPE_orders.read"))))
.andExpect(status().isOk());
// OAuth2 Login: simulate an OIDC-logged-in user hitting a page
mockMvc.perform(get("/dashboard")
.with(oauth2Login()
.authorities(new SimpleGrantedAuthority("ROLE_USER"))
.attributes(a -> a.put("sub", "alice"))))
.andExpect(status().isOk());go deeper
Know that jwt() fakes a bearer-token API caller and oauth2Login() fakes a logged-in user.
Explain the Resource Server vs Login distinction and the no-default-authorities gotcha for jwt().
Cover opaqueToken()/oauth2Client() variants and CSRF differences between stateless and login configs.
Reason about whether to mirror production JwtGrantedAuthoritiesConverter in tests vs setting authorities directly, and the fidelity tradeoff of bypassing the decoder.
## Two different OAuth2 worlds Spring Security supports OAuth2 in two roles, and each has its own test post processor. ### `jwt()` — OAuth2 Resource Server (JWT) A Resource Server is a stateless API that trusts a signed **JWT** (JSON Web Token) presented in the `Authorization: Bearer ...` header. In production a `JwtDecoder` validates the signature, issuer, expiry, etc., then a converter turns claims into a `JwtAuthenticationToken` (an `Authentication` whose principal is a `Jwt`). In tests you don't want to mint real signed tokens. `jwt()` short-circuits all of that: it builds a mock `Jwt` (default token value `"token"`, header `alg: none`, subject `"user"`) and an authenticated `JwtAuthenticationToken`, placing it straight into the `SecurityContext`. The real decoder is never invoked. Customization: ```java get("/api/me").with(jwt(jwt -> jwt .subject("alice") .claim("scope", "read write"))) ``` - `.jwt(Consumer<Jwt.Builder>)` — set claims/headers. - `.authorities(GrantedAuthority...)` or `.authorities(Converter<Jwt, Collection<GrantedAuthority>>)` — override how authorities are derived. **Gotcha:** by default `jwt()` grants NO authorities unless you set them explicitly or supply a converter mirroring your production one. So `.claim("scope", "read")` alone does not automatically produce a `SCOPE_read` authority in the test — you either add `.authorities(new SimpleGrantedAuthority("SCOPE_read"))` or pass your `JwtGrantedAuthoritiesConverter`. ### `oauth2Login()` — OAuth2/OIDC Login (browser flow) This models a user who logged in through an external provider (Google, GitHub, an OIDC IdP) via the authorization-code flow, resulting in a session-based `OAuth2AuthenticationToken` whose principal is an `OAuth2User` (or `OidcUser`). `oauth2Login()` fabricates that state: ```java get("/dashboard").with(oauth2Login() .authorities(new SimpleGrantedAuthority("ROLE_USER")) .attributes(attrs -> attrs.put("sub", "alice")) .oauth2User(...) // supply a full OAuth2User .clientRegistration(...)) // supply a ClientRegistration ``` Defaults: a test `ClientRegistration` with id `"test"` and an `OAuth2User` with attribute `sub=user` and authority `ROLE_USER` (varies slightly by version). Because this is a stateful login flow, mutating requests still need `csrf()`. ### Cousins - `opaqueToken()` — Resource Server using **opaque** tokens introspected via `OpaqueTokenIntrospector`; injects a `BearerTokenAuthentication`. Customize `.attributes(...)` and `.authorities(...)`. - `oauth2Client()` — does NOT authenticate the user; it seeds an `OAuth2AuthorizedClient` into the `OAuth2AuthorizedClientRepository` so a controller parameter annotated `@RegisteredOAuth2AuthorizedClient` (used to call downstream APIs) resolves. ### Choosing - Testing a stateless bearer-token API → `jwt()` (or `opaqueToken()` for opaque tokens). - Testing pages/endpoints behind provider login → `oauth2Login()` / `oidcLogin()`. - Testing a controller that acts as an OAuth2 client calling another service → `oauth2Client()`. ### CSRF interaction Resource-server configs typically disable CSRF (stateless), so `jwt()` requests rarely need `csrf()`. Login-based apps keep CSRF on, so `oauth2Login()` + a mutating method still needs `.with(csrf())`.
- You wrote `jwt(j -> j.claim("scope", "read"))` and expected authority `SCOPE_read`, but authorization fails. Why?By default `jwt()` does not run your production scope→authority conversion; the mock has no authorities unless you set `.authorities(...)` or pass your `JwtGrantedAuthoritiesConverter`. Add the authority explicitly.
- What does `oauth2Client()` do that `oauth2Login()` does not?`oauth2Client()` doesn't authenticate the user; it registers an `OAuth2AuthorizedClient` so a controller method parameter annotated `@RegisteredOAuth2AuthorizedClient` (for outbound API calls) resolves in the test.
saying these in an interview costs you the question
- Claiming `jwt()` validates the token signature (it bypasses the JwtDecoder entirely)
- Assuming a `scope` claim auto-produces SCOPE_ authorities in jwt() tests
- Using oauth2Login() to test a stateless bearer-token API (wrong role)
- Forgetting csrf() on a mutating oauth2Login() request