Welcome to your deep dive into securing modern web applications. In this lesson, we will master the architecture of Spring Security and implement stateless authentication using JSON Web Tokens (JWT).
At its heart, Spring Security is a massive pipeline of Servlet Filters known as the DelegatingFilterProxy. Every incoming HTTP request must pass through this funnel before it ever reaches your business logic (the Controller). Imagine a high-security building where every visitor must pass through multiple checkpoints—ID verification, bag checks, and metal detectors—before entering the lobby.
The core of this process is the FilterChainProxy. When you configure HttpSecurity in your code, you are essentially defining the sequence and the logic of these checkpoints. Common filters include the UsernamePasswordAuthenticationFilter (for standard forms) and the BearerTokenAuthenticationFilter (for JWT).
One common pitfall is misunderstanding the order of these filters. Because Spring Security is built on a chain, if you add a custom filter, you must decide where it sits. If you place your custom JWT filter after the authentication filter, your application won't know the user is authenticated when it hits the standard security checks. Always use .addFilterBefore() or .addFilterAfter() to ensure your security logic executes precisely when needed.
Once a request reaches the security layer, the system needs to verify the identity of the user. This is handled by the AuthenticationManager. Think of this as the "Chief of Security" who delegates verification to specific experts known as AuthenticationProviders.
A provider could be a database lookup, an LDAP server, or an OAuth2 server. When you submit credentials, they are wrapped in an Authentication object. The provider checks these credentials. If valid, it returns an Authenticated object containing the user's GrantedAuthorities (roles or permissions).
In a stateless JWT flow, we bypass the traditional HttpSession by manually populating the SecurityContext after validating the JWT. This tells Spring that the user is who they claim to be for the duration of this single request only.
In a stateless architecture, the server does not remember the user. Instead, the client sends a Authorization: Bearer <token> header with every call. To implement this, we create a custom filter that extends OncePerRequestFilter.
This filter performs four critical steps:
Authorization header.Principal.Note: Never store your secret JWT signing key in plain text within your source code. Use Environment Variables or a Secret Management service like HashiCorp Vault to inject the key at runtime.
A major pitfall here is failing to handle expired tokens. Always include logic to catch ExpiredJwtException and return a 401 Unauthorized status. Failure to do so may lead to the application throwing a 500 error, leaking stack traces to the user.
Now that we have a filter, we must configure which endpoints require authentication. We do this via the SecurityFilterChain bean. This is where you map URL patterns to security constraints.
You should follow the principle of Least Privilege. Deny everything by default and explicitly allow only what is necessary, such as /api/auth/login or /api/public/**.
Common mistake: Forgetting to disable CSRF (Cross-Site Request Forgery) protection. Since your app is stateless and uses JWTs provided in the header rather than cookies, CSRF is generally not applicable, but leaving it enabled can block your API requests if you aren't providing the correct tokens.
The Spring Security architecture relies on a structured sequence of filters to validate incoming web requests before they reach your controller logic. Explain why the specific positioning of a custom JWT authentication filter within the FilterChainProxy is critical to the security of your application, and describe what might go wrong if this filter is placed incorrectly in the processing sequence.