JWT Authentication
with Spring Security
A complete visual guide to implementing stateless authentication — from user registration to token refresh. Built for interview preparation.
Complete Authentication Lifecycle
Step 1
Register
POST /api/user-register
One-time
Step 2
Login
POST /generate-token
Get Tokens
Step 3
Use API
GET /api/* + Bearer
Every Request
Step 4
Refresh
POST /refresh-token
On Expiry
All Components at a Glance
☷
JWTAuthFilter
Handles login
✓
JwtValidationFilter
Validates JWT per request
↻
JWTRefreshFilter
Issues new access token
⚙
SecurityConfig
Wires everything
🔒
DaoAuthProvider
Username + Password
🔑
JWTAuthProvider
JWT token validation
👤
UserDetailsService
Loads user from DB
🔐
JWTUtil
Generate + Validate JWT
Filters
Providers
Services
Utility
Complete Request Journey — Every API Call
This is exactly what happens when a client makes any authenticated API call
➔
➔
☷
Filter Chain
3 Custom Filters
➔
⚙
ProviderManager
Routes by token type
➔
➔
➔
🛡
SecurityContext
ThreadLocal
➔
💻
Controller
Business Logic
➔
Security Filter Chain
Every HTTP request passes through this chain top to bottom
1
JWTAuthenticationFilter
POST /generate-token only
▼
2
JwtValidationFilter
Any request with Bearer header
▼
3
JWTRefreshFilter
POST /refresh-token only
▼
4
Authorization Check
permitAll() or authenticated()
Provider Routing (Strategy Pattern)
ProviderManager routes by token TYPE to the correct provider
Token Type
UsernamePasswordAuthenticationToken
{ username, password }
▼ routes to
DaoAuthenticationProvider
BCrypt verify • Used in Login (Step 2)
Token Type
JwtAuthenticationToken
{ jwt string }
▼ routes to
JWTAuthenticationProvider
Signature verify • Used in Validate (Step 3) & Refresh (Step 4)
Which Filter Handles Which Request?
| Request |
Filter 1: Auth |
Filter 2: Validate |
Filter 3: Refresh |
Result |
| POST /api/user-register |
skip |
skip |
skip |
permitAll() |
| POST /generate-token |
HANDLES ★ |
skip |
skip |
Access + Refresh tokens |
| GET /api/* + Bearer |
skip |
HANDLES ★ |
skip |
SecurityContext set |
| POST /refresh-token |
skip |
skip |
HANDLES ★ |
New access token |
Detailed System Architecture — All Components
SPRING SECURITY JWT SYSTEM
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ FILTERS │ │ PROVIDERS │ │ SERVICES │ │ UTILITY │
├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤
│ JWTAuth │ │ DaoAuth │ │ UserDetails │ │ JWTUtil │
│ Filter │ │ Provider │ │ Service │ │ │
│ (login) │ │ (user+pass) │ │ (DB bridge) │ │ generate() │
│ │ │ │ │ │ │ validate() │
│ JwtValid │ │ JWTAuth │ │ UserAuth │ │ │
│ Filter │ │ Provider │ │ Entity │ │ Password │
│ (API calls) │ │ (JWT token) │ │ Repository │ │ Encoder │
│ │ │ │ │ │ │ (BCrypt) │
│ JWTRefresh │ │ │ │ │ │ │
│ Filter │ │ │ │ │ │ │
│ (refresh) │ │ │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ SecurityConfig — Wires all components, defines filter chain │
└─────────────────────────────────────────────────────────────────┘
Security Filter Chain — Detailed Execution Order
HTTP Request
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ SECURITY FILTER CHAIN │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ FILTER 1: JWTAuthenticationFilter │ │
│ │ Listens for: POST /generate-token │ │
│ │ Purpose: LOGIN — validate credentials, generate tokens │ │
│ │ All other URLs → doFilter() → pass through │ │
│ └──────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ FILTER 2: JwtValidationFilter │ │
│ │ Listens for: ANY request with Authorization: Bearer header │ │
│ │ Purpose: VALIDATE — verify JWT, set SecurityContext │ │
│ │ No header → doFilter() → pass through │ │
│ └──────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ FILTER 3: JWTRefreshFilter │ │
│ │ Listens for: POST /refresh-token │ │
│ │ Purpose: REFRESH — validate cookie, issue new access token │ │
│ │ All other URLs → doFilter() → pass through │ │
│ └──────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Authorization Check (FilterSecurityInterceptor) │ │
│ │ permitAll() paths → allow │ │
│ │ authenticated() paths → check SecurityContext │ │
│ └──────────────────────────┬─────────────────────────────────────┘ │
│ │ │
└─────────────────────────────┼────────────────────────────────────────┘
│
▼
DispatcherServlet → @Controller
Provider Routing — How ProviderManager Delegates (Strategy Pattern)
AuthenticationManager (ProviderManager)
│
│ providers = [ DaoAuthenticationProvider, JWTAuthenticationProvider ]
│
│ Receives Authentication object → checks TYPE → routes to correct provider
│
│ ┌───────────────────────────┐
│ │ JwtAuthenticationToken │
│ │ { token="eyJhbG..." } │
│ │ authenticated=false } │
│ └──────────┬────────────────┘
│ │ │
│ ▼ ▼
│ ┌───────────────────────────────┐ ┌──────────────────────────────────┐
│ │ DaoAuthenticationProvider │ │ JWTAuthenticationProvider │
│ │ │ │ │
│ │ supports(UsernamePassword │ │ supports(JwtAuth │
│ │ AuthToken) → TRUE ✓ │ │ Token) → TRUE ✓ │
│ │ │ │ │
│ │ 1. loadUserByUsername() │ │ 1. Extract token string │
│ │ → DB query │ │ 2. validateAndExtractUsername() │
│ │ 2. passwordEncoder.matches() │ │ → verify signature + expiry │
│ │ → BCrypt verify │ │ 3. loadUserByUsername() │
│ │ 3. Return authenticated token│ │ → DB query │
│ │ │ │ 4. Return authenticated token │
│ │ USED BY: Login (Step 2) │ │ │
│ └───────────────────────────────┘ │ USED BY: Validate (Step 3) │
│ │ Refresh (Step 4) │
│ └──────────────────────────────────┘