Security

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.

01
Architecture

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 login
  • ExceptionTranslationFilter โ€” converts AuthExceptions to HTTP responses
  • FilterSecurityInterceptor โ€” final access decision
You don't need to memorise the full chain โ€” understand that it's ordered and each filter can short-circuit.
Filter Chain as a Pipe
HTTP Request
SecurityContext
JWT Filter (yours)
Auth Filter
Exception Filter
Access Check
Controller
02
Concepts

Authentication vs Authorisation

Authentication

Who are you? โ†’ Authentication object in SecurityContext

Authorisation

What are you allowed to do? โ†’ roles and authorities checked against the request

  • SecurityContextHolder โ€” thread-local storage for the current user's Authentication
  • UserDetailsService โ€” the bridge between Spring Security and your user store
03
Rules

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 STATELESS for REST APIs
Access Decision Tree
Request arrives โ†“ URL in permitAll list? โ†’ Yes โ†’ Allow โ†“ No User authenticated? โ†’ No โ†’ 401 โ†“ Yes Has required role? โ†’ No โ†’ 403 โ†“ Yes Allow through to controller
04
Token Auth

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.

Client โ†’ POST /auth/login {username, password} โ† 200 { token: "eyJ..." }
JWT Validation Sequence
GET /api/orders
Bearer eyJ...
JwtAuthFilter
Validate Sig + Exp
Set SecurityContext
Controller
200 [orders]
05
Delegation

OAuth2 / OIDC

Resource Owner (user), Client (your app), Authorization Server (Keycloak/Auth0/Google), Resource Server (your API).

ModeStarterUse 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
OIDC adds identity on top of OAuth2 โ€” id_token contains user profile claims. One property enables JWT validation: spring.security.oauth2.resourceserver.jwt.issuer-uri.
06
Pitfalls

Common Misconfigurations

1. CSRF not disabled for REST APIs
Stateless JWT APIs don't need CSRF protection (no session cookie to forge). Leaving it enabled causes 403s on POST/PUT/DELETE.
2. permitAll() doesn't bypass all filters
The request still runs through the filter chain. A JWT filter that throws on a missing token before permitAll() is checked will still block it.
3. hasRole('ADMIN') vs hasAuthority('ROLE_ADMIN')
hasRole prepends ROLE_ automatically. Don't double-prefix.
4. @PreAuthorize not working
@EnableMethodSecurity is required. Forgetting it means the annotation is silently ignored.