The Security Filter Chain
The Security Filter Chain
Every HTTP request that reaches a Spring Boot application passes through a pipeline of servlet filters before it ever touches your controller. Spring Security plugs into this pipeline with its own ordered chain of filters — the Security Filter Chain. Understanding that chain is essential: it determines what happens to unauthenticated requests, how credentials are extracted, how sessions are managed, and what error responses are sent. If something security-related behaves unexpectedly, the filter chain is almost always where you need to look.
How Servlet Filters Work
The Java Servlet specification defines a Filter interface with one method: doFilter(request, response, chain). A filter can inspect or modify the request, call chain.doFilter() to pass control to the next filter in line, and then inspect or modify the response on the way back out. Filters are ordered, and each one wraps the next — forming a true chain.
Spring Security registers a single servlet filter called DelegatingFilterProxy into the standard servlet container filter chain. That proxy delegates all work to a Spring-managed bean named springSecurityFilterChain. Behind that bean sits FilterChainProxy, which holds one or more SecurityFilterChain instances — each a list of Spring Security-specific filters.
The Default Filter Order
When you add spring-boot-starter-security to a project, Spring Boot auto-configures a SecurityFilterChain with the following filters (abbreviated, in order):
DisableEncodeUrlFilter— prevents session IDs from leaking into URLs.WebAsyncManagerIntegrationFilter— propagates theSecurityContextto async threads.SecurityContextHolderFilter— loads theSecurityContextfrom the repository (typically the HTTP session) at the start of the request and clears it afterwards.HeaderWriterFilter— adds security-related HTTP response headers (X-Frame-Options, X-Content-Type-Options, etc.).CsrfFilter— validates CSRF tokens on state-changing requests.LogoutFilter— intercepts the logout URL and clears the security context and session.UsernamePasswordAuthenticationFilter— processes form-login POST requests.DefaultLoginPageGeneratingFilter— serves the built-in login form (removed when you supply your own).BearerTokenAuthenticationFilter— extracts JWT/opaque tokens from theAuthorization: Bearerheader (present only when OAuth2 Resource Server is configured).RequestCacheAwareFilter— replays the original request after a successful login redirect.SecurityContextHolderAwareRequestWrapper— wraps the request to expose the Servlet security API.AnonymousAuthenticationFilter— if no authentication has been set yet, inserts an anonymous principal so downstream code never sees a null.ExceptionTranslationFilter— catchesAccessDeniedExceptionandAuthenticationExceptionand converts them into HTTP responses (401/302 or 403).AuthorizationFilter— enforces the access rules you declared inSecurityFilterChain.
How a Request Flows Through the Chain
Consider an unauthenticated GET request to a protected resource:
SecurityContextHolderFilterlooks for an existingSecurityContextin the session — finds none, so it sets an empty context.- Authentication filters (UsernamePassword, Bearer, etc.) look for credentials in the request — find none, so they pass through without setting a principal.
AnonymousAuthenticationFiltersees that no authentication is set; it inserts anAnonymousAuthenticationToken.AuthorizationFilterevaluates the rule for the requested URL (e.g.,authenticated()) against the anonymous token — access is denied.ExceptionTranslationFiltercatches theAccessDeniedException. Because the current principal is anonymous, it treats this as a missing authentication and redirects the browser to the login page (or returns 401 for stateless APIs).
Now consider a POST to /login with valid credentials:
UsernamePasswordAuthenticationFiltermatches the URL, extracts username and password, delegates to theAuthenticationManager.- The
AuthenticationManagerloads the user viaUserDetailsService, verifies the password, and returns a populatedAuthenticationobject. - The filter stores this in the
SecurityContextand saves the context to the session. - The configured
AuthenticationSuccessHandlerredirects the user to their original destination.
Multiple SecurityFilterChain Beans
Spring Security 6 allows you to define multiple SecurityFilterChain beans, each matched to a different URL pattern. This is the idiomatic way to have different security rules for, say, your REST API (stateless JWT) and your admin web UI (stateful form login).
FilterChainProxy evaluates each chain's securityMatcher in @Order order and uses the first matching chain. Subsequent chains are not consulted. Forgetting to set a matcher on a lower-order chain effectively makes it a catch-all and can swallow requests intended for your other chains.
@Order Spring will throw an exception at startup. Always assign an explicit and unique order to every SecurityFilterChain bean in a multi-chain setup.
Adding a Custom Filter
You can insert your own filter at a specific position in the chain using addFilterBefore, addFilterAfter, or addFilterAt. A common use case is validating a custom request header before the standard authentication filters run:
Extending OncePerRequestFilter guarantees your filter runs exactly once per request — important in servlet containers that may dispatch internally (e.g., forward, error dispatches).
Inspecting the Chain at Runtime
During development you can log every filter that processes a request by setting the Spring Security debug flag:
Or at the annotation level:
debug = true in production. It logs full request details — headers, parameters, and security decisions — which can expose sensitive information in log aggregators.
Security Context and Thread Propagation
The SecurityContext is stored in a ThreadLocal by default. This means it is available anywhere in the call stack of the same thread — your controllers, services, and repositories — without passing it explicitly. However, if you spawn a new thread (e.g., with CompletableFuture, @Async, or a virtual thread), the context is not automatically inherited. Spring Security provides DelegatingSecurityContextExecutor and the MODE_INHERITABLETHREADLOCAL strategy to handle this, which is covered in the Method-Level Security lesson.
Summary
The Security Filter Chain is the backbone of Spring Security. Every request is intercepted by DelegatingFilterProxy, routed through FilterChainProxy, and processed by an ordered list of filters that load the security context, attempt authentication, fall back to anonymous, and then enforce authorization. Multiple chains let you apply different security strategies to different URL namespaces. Custom filters slot cleanly into this chain at any position. In the next lesson you will sharpen the distinction between authentication (who are you?) and authorization (what can you do?).