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:
- ChannelProcessingFilter: Handles protocol redirection requirements
- SecurityContextPersistenceFilter: Sets up SecurityContext at the beginning of a request and copies changes to HttpSession when the request ends
- ConcurrentSessionFilter: Updates SessionRegistry to reflect ongoing requests from the principal
- Authentication Processing Mechanisms: Includes UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter, etc.
- SecurityContextHolderAwareRequestFilter: Installs Spring Security aware HttpServletRequestWrapper
- JaasApiIntegrationFilter: Processes JaasAuthenticationToken
- RememberMeAuthenticationFilter: Handles remember-me services
- AnonymousAuthenticationFilter: Sets up anonymous Authentication object
- ExceptionTranslationFilter: Catches security exceptions and returns HTTP error responses or launches AuthenticationEntryPoint
- 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.