A JWT Authentication Filter
A JWT Authentication Filter
In a stateless Spring Security application every incoming HTTP request must carry proof of identity — there is no server-side session to look up. The component responsible for examining that proof on each request is a servlet filter. In this lesson you will build a production-quality JwtAuthenticationFilter that sits early in Spring Security's filter chain, extracts the token from the Authorization header, validates it, and populates Spring's SecurityContext so the rest of the framework behaves as if a session-authenticated user were present.
Where the Filter Lives in the Chain
Spring Security processes every request through an ordered chain of filters before it ever reaches a controller. By extending OncePerRequestFilter — a Spring convenience class that guarantees exactly one execution per request, even across forward or include dispatches — your filter integrates cleanly with that chain. You then tell Spring Security to run it before UsernamePasswordAuthenticationFilter so credentials are resolved before any subsequent filter tries to redirect to a login page.
OncePerRequestFilter and not a plain Filter? A raw javax.servlet.Filter can be invoked multiple times in a single HTTP exchange if the request is forwarded internally (e.g. error dispatches). OncePerRequestFilter uses a request-scoped flag to prevent that, which matters for security: you do not want authentication logic running twice with potentially inconsistent state.
Extracting the Token from the Request
The OAuth 2.0 Bearer Token specification (RFC 6750) defines how a token should be transmitted: in an HTTP header named Authorization with the value Bearer <token>. Your filter must read that header and strip the prefix.
Returning null here is intentional: many requests (static assets, public endpoints) will not carry a token, and the filter should simply pass them through without error. Authentication is attempted; it is the security configuration that decides whether a missing token is a problem.
The Full Filter Implementation
Here is the complete filter. Read it top-to-bottom; every design decision is explained below.
Step-by-Step Explanation
Step 1–2 — Extract or pass through. If no Authorization: Bearer … header is present the filter does nothing and calls filterChain.doFilter(). Downstream security rules (in SecurityFilterChain) will enforce authentication where needed.
Step 3 — Idempotency check. If the SecurityContext already holds an authentication object — for example because another filter ran first, or the same thread handled an earlier request — there is nothing to do. Skipping this check would overwrite a valid authentication unnecessarily.
Step 4 — Extract the subject. jwtService.extractUsername() parses the JWT and returns the sub claim. This operation does not yet prove the token is valid; it only reads it.
Step 5 — Load UserDetails. The database is the authority on whether the user still exists and is active. A token for a deleted or locked account must be rejected, and the UserDetails loaded here carries that information.
Step 6 — Validate. jwtService.isTokenValid() checks the signature, the expiry, and (optionally) that the username in the token matches the loaded UserDetails. Only when all checks pass does authentication proceed.
Step 7 — Populate the SecurityContext. UsernamePasswordAuthenticationToken is constructed with three arguments: principal (UserDetails), credentials (null — there is no password to carry at this point), and authorities. Passing the authorities collection is what tells Spring Security the user is authenticated; the no-arg constructor creates an unauthenticated token.
WebAuthenticationDetailsSource. Attaching request details (IP address, session ID) to the authentication object enables audit logging and is used by some security event listeners. It costs nothing and provides valuable context.
Step 8 — Always continue. The filter never short-circuits the chain by sending a 401 directly (except by omission — if validation fails, no authentication is set, and Spring Security's AuthenticationEntryPoint handles the 401 response further down). This keeps error-handling logic in one place.
Registering the Filter in the Security Configuration
In Spring Security 6 you register the filter inside your SecurityFilterChain bean using addFilterBefore:
Authorization header (not a cookie), the browser will never send it cross-site automatically, so CSRF is not a threat here. If you ever switch to storing JWTs in cookies, you must re-enable CSRF protection.
Security Implications and Distributed-Systems Trade-offs
The database lookup in step 5 is a deliberate trade-off. Pure stateless JWT validation would skip it, but then a token for a deleted or disabled user would remain valid until expiry. Loading UserDetails on every request catches that case at the cost of one extra database query per call. Mitigate the cost with a short-lived in-memory or Redis cache keyed on the username.
In a microservices environment each service typically has its own instance of this filter. The JWT is validated locally (signature verification is CPU-only; no network hop), while the UserDetails lookup may be replaced by reading claims directly from the token — roles and permissions are often embedded as claims so services need not reach out to a user service on every call. Lesson 6 covers that pattern in detail.
Summary
Your JwtAuthenticationFilter is the bridge between a raw HTTP request and Spring Security's authentication model. It extracts the token, validates it through JwtService, loads the user from the database, and populates the SecurityContext — all in a single, idempotent, side-effect-free pass. Register it before UsernamePasswordAuthenticationFilter with STATELESS session policy and Spring Security handles the rest: authorisation rules, 401 responses, and method-level security all work without any further changes.