What is the Spring Security filter chain, and how does a request pass through it?

8 minintermediatefilter-chainspring-securitybasics

Quick Answer

Spring Security intercepts every incoming request through a chain of servlet Filters (a SecurityFilterChain), registered as a single delegating filter (DelegatingFilterProxy/FilterChainProxy) in front of the application's normal servlet dispatch. Each filter in the chain handles one specific concern in order — e.g., parsing an authentication token, checking CSRF, enforcing authorization rules — and a request only reaches the actual controller once it has passed every applicable filter, or is rejected/redirected early (e.g., with a 401/403) if a filter determines it shouldn't proceed.

Detailed Answer

Spring Security is implemented as a chain of servlet Filters, registered as a single entry point (DelegatingFilterProxy, which delegates to a Spring-managed FilterChainProxy) in front of the rest of the application — meaning security logic runs before a request ever reaches Spring MVC's DispatcherServlet.

A SecurityFilterChain is an ordered list of individual filters, each responsible for one specific concern, roughly in this order for a typical configuration:

  1. SecurityContextPersistenceFilter/SecurityContextHolderFilter — loads any existing security context (e.g., from a session) for the request.
  2. CorsFilter — handles CORS preflight/actual request headers, if configured.
  3. CsrfFilter — validates a CSRF token for state-changing requests, if CSRF protection is enabled.
  4. Authentication filters — e.g., UsernamePasswordAuthenticationFilter (form login), or a custom JWT-parsing filter — attempt to authenticate the request and populate the SecurityContext with an Authentication object if successful.
  5. ExceptionTranslationFilter — catches authentication/authorization exceptions thrown further down the chain and converts them into an appropriate HTTP response (401 for unauthenticated, 403 for unauthorized) or redirect (e.g., to a login page).
  6. FilterSecurityInterceptor/AuthorizationFilter — makes the final authorization decision for the specific request, based on configured access rules, before allowing it to proceed to the actual application.
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/public/**").permitAll()
            .anyRequest().authenticated())
        .formLogin(Customizer.withDefaults());
    return http.build();
}

Only once a request has passed every applicable filter in the chain does it actually reach Spring MVC's DispatcherServlet and, eventually, your controller method — if any filter determines the request should be rejected (missing/invalid credentials, insufficient authority), it short-circuits the chain and returns an error response directly, without the controller ever being invoked.

You can register multiple SecurityFilterChain beans, each matched to a different RequestMatcher (e.g., one chain for /api/** using stateless JWT auth, a separate chain for the rest of the app using session-based form login) — a common pattern for applications that need to support more than one authentication style simultaneously.