Deep Dive into Spring Security Filter Chain Mechanism and JWT Integration

Nov 23, 2025 · Programming · 6 views · 7.8

Keywords: Spring Security | Filter Chain | JWT Authentication | Authentication | Authorization Mechanism

Abstract: This article provides an in-depth analysis of the Spring Security filter chain working mechanism, detailing the execution order and functionality of key filters including SecurityContextPersistenceFilter and UsernamePasswordAuthenticationFilter. Through practical configuration examples, it demonstrates the auto-configuration process of form-login and focuses on JWT token authentication integration solutions, covering custom filter development, multi-authentication mechanism coexistence strategies, and SecurityContext persistence customization methods. The article includes complete code implementations and configuration examples, offering comprehensive guidance for security framework customization.

Spring Security Filter Chain Foundation

Spring Security's security mechanism is built upon the Servlet filter chain foundation, where DelegatingFilterProxy connects multiple security filters into a complete processing pipeline. This filter chain is responsible for intercepting HTTP requests, performing authentication and authorization checks, and ultimately deciding whether to allow requests to access target resources.

Core Filter Execution Order and Functions

According to the official Spring Security documentation, the complete filter chain execution order is as follows:

  1. ChannelProcessingFilter: Handles protocol redirection requirements
  2. SecurityContextPersistenceFilter: Sets up SecurityContext at the beginning of a request and copies changes to HttpSession when the request ends
  3. ConcurrentSessionFilter: Updates SessionRegistry to reflect ongoing requests from the principal
  4. Authentication Processing Mechanisms: Includes UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter, etc.
  5. SecurityContextHolderAwareRequestFilter: Installs Spring Security aware HttpServletRequestWrapper
  6. JaasApiIntegrationFilter: Processes JaasAuthenticationToken
  7. RememberMeAuthenticationFilter: Handles remember-me services
  8. AnonymousAuthenticationFilter: Sets up anonymous Authentication object
  9. ExceptionTranslationFilter: Catches security exceptions and returns HTTP error responses or launches AuthenticationEntryPoint
  10. FilterSecurityInterceptor: Protects web URIs and raises exceptions when access is denied

form-login Configuration and Filter Auto-assembly

When configuring the <security:http> element, even with minimal configuration, the default filter chain is automatically configured:

<security:http authentication-manager-ref="mainAuthenticationManager" 
               entry-point-ref="serviceAccessDeniedHandler">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>

At this point, the filter chain contains core components like SecurityContextPersistenceFilter, ExceptionTranslationFilter, and FilterSecurityInterceptor, but lacks authentication processing mechanisms. After adding <form-login> configuration:

<security:http authentication-manager-ref="mainAuthenticationManager">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
    <security:form-login />
</security:http>

The system automatically adds UsernamePasswordAuthenticationFilter and DefaultLoginPageGeneratingFilter to the filter chain.

JWT Token Authentication Integration Solution

For REST API JWT token authentication, a single filter chain configuration approach can be adopted. By creating a custom JwtAuthenticationFilter that coexists with UsernamePasswordAuthenticationFilter in the same filter chain:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) 
            throws ServletException, IOException {
        
        // Check if request is already authenticated
        if (SecurityContextHolder.getContext().getAuthentication() != null) {
            filterChain.doFilter(request, response);
            return;
        }
        
        String token = resolveToken(request);
        if (token != null && tokenProvider.validateToken(token)) {
            String username = tokenProvider.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            
            UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        
        filterChain.doFilter(request, response);
    }
    
    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

Multi-authentication Mechanism Coexistence Strategy

Multiple authentication processing mechanisms can be configured in the same filter chain. Each authentication filter checks at the beginning of the doFilter() method whether the request is already authenticated to avoid duplicate authentication:

public class MultiAuthFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) 
            throws ServletException, IOException {
        
        // Key check: avoid duplicate authentication
        if (SecurityContextHolder.getContext().getAuthentication() != null 
            && SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
            filterChain.doFilter(request, response);
            return;
        }
        
        // Execute specific authentication logic
        // ...
        
        filterChain.doFilter(request, response);
    }
}

SecurityContext Persistence Customization

To replace the Session-based SecurityContextPersistenceFilter with a JWT solution, configure stateless sessions and customize SecurityContextRepository:

<security:http create-session="stateless">
    <security:custom-filter ref="jwtSecurityContextRepositoryFilter" position="SECURITY_CONTEXT_FILTER"/>
</security:http>

<bean id="jwtSecurityContextRepositoryFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
    <constructor-arg ref="jwtSecurityContextRepository"/>
</bean>

<bean id="jwtSecurityContextRepository" class="com.example.JwtSecurityContextRepository"/>

Custom SecurityContextRepository implementation:

public class JwtSecurityContextRepository implements SecurityContextRepository {
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        
        String token = extractToken(requestResponseHolder.getRequest());
        if (token != null && tokenProvider.validateToken(token)) {
            String username = tokenProvider.getUsernameFromToken(token);
            // Load user details and set authentication information
            // ...
        }
        
        return context;
    }
    
    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        // JWT stateless, no need to save to Session
    }
    
    @Override
    public boolean containsContext(HttpServletRequest request) {
        return extractToken(request) != null;
    }
    
    private String extractToken(HttpServletRequest request) {
        // Extract JWT token from request
        // ...
    }
}

Filter Chain Configuration Best Practices

In actual projects, the following configuration strategy is recommended:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .formLogin(form -> form
                .loginProcessingUrl("/api/login")
                .permitAll()
            )
            .csrf(csrf -> csrf.disable());
        
        return http.build();
    }
    
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }
}

This configuration ensures that JWT authentication and form login coexist harmoniously in the same filter chain while maintaining the system's stateless characteristics.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.