Filter Chain, JWT, OAuth2
The mental model for how Spring Security works โ the filter chain โ plus the two auth patterns you'll implement on every project.
Security Filter Chain
Every HTTP request passes through an ordered chain of filters before reaching your controller.
You configure a SecurityFilterChain bean โ not WebSecurityConfigurerAdapter (deprecated in Boot 3).
SecurityContextPersistenceFilterโ loads/stores SecurityContext- Your custom JWT filter (insert here)
UsernamePasswordAuthenticationFilterโ form loginExceptionTranslationFilterโ converts AuthExceptions to HTTP responsesFilterSecurityInterceptorโ final access decision
Authentication vs Authorisation
Who are you? โ Authentication object in SecurityContext
What are you allowed to do? โ roles and authorities checked against the request
SecurityContextHolderโ thread-local storage for the current user'sAuthenticationUserDetailsServiceโ the bridge between Spring Security and your user store
Configuring Access
SecurityFilterChain bean with HttpSecurity โ the modern fluent API.
requestMatchers("/public/**").permitAll(), .anyRequest().authenticated().
@PreAuthorize("hasRole('ADMIN')") โ enable with @EnableMethodSecurity.
Use @PreAuthorize over @Secured or @RolesAllowed โ it supports SpEL expressions.
- CSRF: enabled by default โ disable for stateless REST APIs (JWT-based, no session cookie)
- Session management: set to
STATELESSfor REST APIs
JWT Flow
Header.Payload.Signature โ the payload contains claims: sub (userId),
roles, iat (issued at), exp (expiry).
The server validates the signature โ no session lookup needed. Stateless.
Bearer eyJ...
OAuth2 / OIDC
Resource Owner (user), Client (your app), Authorization Server (Keycloak/Auth0/Google), Resource Server (your API).
| Mode | Starter | Use Case |
|---|---|---|
| OAuth2 Client | spring-boot-starter-oauth2-client | "Login with Google" โ your app IS the client |
| Resource Server | spring-boot-starter-oauth2-resource-server | Your API validates JWTs from an auth server |
id_token contains user profile claims.
One property enables JWT validation: spring.security.oauth2.resourceserver.jwt.issuer-uri.
Common Misconfigurations
Stateless JWT APIs don't need CSRF protection (no session cookie to forge). Leaving it enabled causes 403s on POST/PUT/DELETE.
permitAll() doesn't bypass all filtersThe request still runs through the filter chain. A JWT filter that throws on a missing token before
permitAll() is checked will still block it.
hasRole('ADMIN') vs hasAuthority('ROLE_ADMIN')hasRole prepends ROLE_ automatically. Don't double-prefix.
@PreAuthorize not working@EnableMethodSecurity is required. Forgetting it means the annotation is silently ignored.