Hello! Welcome back to our module on Spring Security.
In our last lesson, we explored the theory behind JSON Web Tokens (JWTs), understanding their structure and their role in enabling stateless authentication. You learned that a JWT is a compact, self-contained token that allows servers to verify requests without maintaining session state, making it ideal for scalable microservices and modern web applications.
Today, we will transition from theory to practice. Your learning outcome is to implement a stateless JWT authentication filter in a Spring Boot application. We will build the core components that intercept incoming requests, validate a JWT, and integrate with the Spring Security context to secure our REST APIs. This is a cornerstone skill for any backend developer and a frequent deep-dive topic in technical interviews.
A Quick Recap: The Spring Security Filter Chain
Before we write any code, let's quickly refresh our memory on the Spring Security architecture. As we've discussed, every request to a secured application passes through a series of filters.
Spring Security Architecture Explained
To set the stage, let's watch a brief segment from this Amigoscode video. It provides an excellent visual recap of the filter chain and introduces the concept of a dedicated JWT filter, which is exactly what we're about to build.
Watch from 01:47 to 04:06. Focus on how a request passes through a chain of filters and how a specific JwtAuthenticationFilter is responsible for checking the token before the request can proceed.
Our goal is to create and insert our own custom filter into this chain. This filter's single responsibility will be to look for a JWT in the request headers, validate it, and if it's valid, establish the user's identity for that request.
1. Configuring the SecurityFilterChain for Statelessness
First, we need to configure Spring Security to behave in a stateless manner. Since Spring Security 5.7, this is done by defining a SecurityFilterChain bean, which replaces the older WebSecurityConfigurerAdapter.
In your security configuration class (e.g., SecurityConfig), you'll define a bean like this:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 1. Disable CSRF, as we are stateless and don't use sessions/cookies.
.csrf(csrf -> csrf.disable())
// 2. Set session management to stateless.
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 3. Define authorization rules for our endpoints.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll() // Login/register endpoints are public.
.anyRequest().authenticated() // All other requests require authentication.
);
// We will add our custom JWT filter here later.
return http.build();
}
}
Key Points for Interviews:
- CSRF (
.csrf().disable()): CSRF protection works by using a unique token stored in the user's session. Since we are building a stateless API and disabling sessions, this mechanism is not needed and can be disabled. - Stateless Session (
.sessionCreationPolicy(STATELESS)): This is the most critical setting. It tells Spring Security not to create or use anyHttpSession. Each request must be authenticated independently, which is the core principle of token-based authentication.
2. The Heart of the Matter: The JwtAuthenticationFilter
Now, let's create the filter itself. Best practice is to extend OncePerRequestFilter, which guarantees the filter is executed only once for any given request.
This filter will perform the following steps for every incoming request:
- Check for an
Authorizationheader containing a Bearer token. - If the token exists, validate it.
- If the token is valid, load the associated user from the database.
- Set the authenticated user in Spring Security's
SecurityContextHolder.
The logic within this filter is crucial, and the Toptal article "Spring Security With JWT for REST API" provides a clear, classic implementation.
Spring Security With JWT for REST API
Let's study the implementation of a JwtTokenFilter from this Toptal article. It provides an excellent template for the logic we need to implement. We will adapt this slightly for the latest Spring Security version.
Read the section 'Authentication Using JWT with Spring Security'. Focus on the code for the JwtTokenFilter class. Pay close attention to the doFilterInternal method and understand its flow: get header, validate token, get user identity, and set the authentication context.
Let's implement our version, JwtAuthenticationFilter.java:
@Component
@RequiredArgsConstructor // Lombok annotation for constructor injection
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService; // Our service to handle JWTs
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
// 1. Check if the Authorization header is present and correctly formatted
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response); // If not, pass to the next filter
return;
}
jwt = authHeader.substring(7); // Extract the token from "Bearer "
userEmail = jwtService.extractUsername(jwt); // Extract username from token
// 2. Check if user is extracted and not already authenticated
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
// 3. If token is valid, update the SecurityContextHolder
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null, // We don't have credentials
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// This is the crucial step that authenticates the user for the request
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
This filter depends on a JwtService for token operations and a UserDetailsService (which you built in a previous lesson) to fetch user data.

3. Creating Supporting Components
Our filter won't work in isolation. It needs a way to generate, parse, and validate tokens. We'll follow the modern and secure approach of using asymmetric keys (RSA).
The video below by Dan Vega is an end-to-end guide. We will use it to implement the necessary components: the key generation, the configuration properties, the JwtEncoder/JwtDecoder beans, and the login endpoint.
Spring Security JWT: How to secure your Spring Boot REST APIs with JSON Web Tokens
This comprehensive video walks through the entire process of setting up JWT with RSA keys in Spring Boot. We will follow its steps to create our token generation and validation logic.
Follow along with the video, but focus on these specific segments: Generate Keys (16:24 - 21:26): Learn how to generate RSA public/private key pairs using openssl. This is a standard practice. Configure Keys (21:26 - 24:26): See how to create a @ConfigurationProperties class to load these keys into your application. Create JwtDecoder and JwtEncoder (24:26 - 29:04): This is key. You'll define beans that Spring Security uses to read (decode) and create (encode) JWTs. Create a TokenService (29:04 - 31:52): Encapsulate the token generation logic in a dedicated service. Create the Login Endpoint (31:52 - 34:24): Implement a /token or /login endpoint that uses AuthenticationManager and your TokenService to issue tokens.
After following the video, you will have:
- RSA key files (
private.pem,public.pem). - Configuration in
application.propertiespointing to these keys. JwtEncoderandJwtDecoderbeans in yourSecurityConfig.- A
TokenService(orJwtService) to generate tokens. - A controller with a login endpoint that returns a JWT upon successful authentication.
4. Integrating the Filter into the Security Chain
The final step is to tell Spring Security to use our custom filter. We do this by adding it to the SecurityFilterChain bean we defined earlier.
Update your securityFilterChain method:
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider; // Assumes you have this bean defined
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider) // Set the auth provider
// Add our custom JWT filter before the standard UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
addFilterBefore(...) is the key. We are inserting our JwtAuthenticationFilter into the chain before the UsernamePasswordAuthenticationFilter. This ensures that for every request, we first check for a valid JWT. If one is found and validated, the user is authenticated, and subsequent filters will see this. If no JWT is present, the request proceeds down the chain, and if it hits a protected endpoint, an authentication error will be triggered as expected.
Test your understanding!
An interviewer asks: "In your JwtAuthenticationFilter, you check if a user is already authenticated with SecurityContextHolder.getContext().getAuthentication() == null. Why is this check important, especially considering you extend OncePerRequestFilter?"
Show answer
While OncePerRequestFilter guarantees the filter is executed only once per request dispatch, a single request can be forwarded internally between servlets (e.g., in case of an error). In such scenarios, the filter could technically run again.
The check SecurityContextHolder.getContext().getAuthentication() == null serves as a performance optimization and a safeguard. It ensures that we don't repeat the expensive work of parsing a token and hitting the database to load UserDetails if the authentication has already been established earlier in the chain or during a previous run of the filter within the same request.
Conclusion
Congratulations! You have now implemented the complete flow for stateless JWT authentication in a Spring Boot application. This is a robust and scalable solution used widely in the industry.
Key Takeaways:
- Stateless Configuration: You must configure Spring Security's
SecurityFilterChainto be stateless by disabling CSRF and setting the session creation policy toSTATELESS. - The Custom Filter is Key: The
JwtAuthenticationFilter(extendingOncePerRequestFilter) is the central component that intercepts requests, extracts the JWT, and validates it. SecurityContextHolder: The primary goal of the filter upon successful token validation is to create aUsernamePasswordAuthenticationTokenand set it in theSecurityContextHolder. This is how you integrate with the rest of Spring Security.- Filter Order Matters: You must add your custom filter at the correct position in the filter chain, typically before
UsernamePasswordAuthenticationFilter, usingaddFilterBefore. - Supporting Beans: A complete solution requires supporting components like
JwtEncoder,JwtDecoder, and a service to encapsulate token logic, often backed by RSA keys for security.
Preview of the Next Lesson:
With our authentication mechanism in place, the server now knows who the user is. The next logical step is to control what the user can do. In the next lesson, "Implement method-level security using @PreAuthorize", we will leverage the authorities (roles) embedded in our JWT to enforce fine-grained access control on specific service methods.