Spring Security In Action — Second Edition
public class JwtAuthenticationFilter extends OncePerRequestFilter @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException !header.startsWith("Bearer ")) chain.doFilter(request, response); return; String token = header.substring(7); String username = jwtService.extractUsername(token); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) UserDetails user = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); // Critical: Set the context for this single request SecurityContextHolder.getContext().setAuthentication(auth); chain.doFilter(request, response); // No cleanup needed because STATELESS means the context dies with the request
The most critical piece from the second edition is the custom filter. It intercepts every request, grabs the Authorization: Bearer header, and populates the SecurityContextHolder for that request only (because there is no session to carry it forward).
@Component public class JwtService private final SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); private final long EXPIRATION = 86400000; // 24 hours public String generateToken(String username) return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(key) .compact(); spring security in action second edition
public String extractUsername(String token) return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody() .getSubject();
In the first edition of Spring Security in Action , many readers fell in love with the classic "formLogin" flow. But in the second edition, Laurentiu Spilca makes one thing crystal clear: In a modern cloud-native world, servers must forget. But in the second edition, Laurentiu Spilca makes
To go stateless, we need to disable session creation entirely:
@Configuration @EnableWebSecurity public class StatelessSecurityConfig @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception http .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(auth -> auth .requestMatchers("/login", "/refresh").permitAll() .anyRequest().authenticated() ); // No formLogin() - we use a custom filter return http.build(); But in the second edition
With sessions disabled, every request must carry its own proof of identity. Here is a simplified implementation of a JWT service as described in the book: