skip to content

Security Filter Ordering

The security filters run in a fixed order — context, CSRF, authentication, exception translation, authorization — and addFilterBefore or After inserts yours at the right point. Placing a custom JWT filter correctly is the standard practical exercise.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is the Spring Security filter chain, and why does the ORDER of filters in it matter?

level: juniorimportance: must knowfreq 70%

answer

  1. FilterChainProxy = one servlet filter delegating to a list
  2. Each filter one job; later reads earlier's state
  3. Load context -> CSRF -> authenticate -> translate -> authorize
  4. SecurityContextHolder is ThreadLocal, bracketed early/late
  5. You never number filters yourself

basics

~20 s

Spring Security is a chain of servlet filters run in a fixed order. Each filter does one job (load context, check CSRF, authenticate, authorize). Order matters because later filters depend on what earlier ones set up — e.g. you must load the user before checking permissions.

solid answer

~40 s

Spring Security plugs into the servlet container as one filter (the FilterChainProxy) that delegates to an ordered list of internal filters — the SecurityFilterChain. Each filter has a single responsibility and runs in a deterministic order defined by FilterOrderRegistration. Order matters because filters build on each other: SecurityContextHolderFilter loads the SecurityContext (the current authentication) first; CsrfFilter validates the token; authentication filters like UsernamePasswordAuthenticationFilter populate the Authentication; ExceptionTranslationFilter wraps the tail to catch security exceptions; and AuthorizationFilter runs last to make the access decision once identity is known. Reorder them wrong and you'd authorize before authenticating, or lose exception handling. You never hand-number these filters — Spring places built-ins automatically and you insert custom ones relative to a known filter.

code

java · 18 lines
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // Each of these DSL calls contributes a filter, placed by
            // Spring in its predefined order (you don't order them).
            .csrf(Customizer.withDefaults())                 // CsrfFilter
            .httpBasic(Customizer.withDefaults())            // BasicAuthenticationFilter
            .formLogin(Customizer.withDefaults())            // UsernamePasswordAuthenticationFilter
            .authorizeHttpRequests(auth -> auth              // AuthorizationFilter (runs last)
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated());
        return http.build();
    }
}

go deeper

for a junior

Know it's an ordered chain of filters, each doing one job, and that order matters because later filters depend on earlier ones.

for a middle

Name the key filters and their sequence, and that FilterChainProxy is the single registered servlet filter.

for a senior

Explain the shared thread-local state (SecurityContextHolder) and why the ordering is load-bearing per filter.

for a principal

Discuss FilterOrderRegistration as the source of truth and how DSL feature toggles add/remove filters deterministically.

## The big picture Spring Security does not intercept requests with magic — it registers a single standard **servlet `Filter`** with the container, named **`springSecurityFilterChain`** (an instance of **`FilterChainProxy`**). Every HTTP request passes through it. `FilterChainProxy` matches the request against one or more **`SecurityFilterChain`** objects and runs that chain's ordered list of internal Spring Security filters before (optionally) continuing to your controller. A **servlet filter** is a class implementing `jakarta.servlet.Filter` with a `doFilter(request, response, chain)` method. It can inspect/modify the request, then either call `chain.doFilter(...)` to continue, or short-circuit (e.g. send a 401). Spring Security is built as a *pipeline* of ~30 such filters, each with one narrow job. ## Why order is not arbitrary The filters share state through two thread-bound holders: - **`SecurityContextHolder`** — holds the `SecurityContext`, which holds the `Authentication` (who the caller is). - The request/response themselves. Because each filter reads what earlier filters wrote, the sequence is load-bearing. The canonical early-to-late flow: 1. **`SecurityContextHolderFilter`** — loads a previously saved `SecurityContext` (e.g. from the `HttpSession`) into `SecurityContextHolder` at the start of the request and clears it at the end. (In Spring Security 6 this replaced the older `SecurityContextPersistenceFilter`; it no longer *creates* a context eagerly.) Must run early so everything downstream sees the current user. 2. **`CsrfFilter`** — for state-changing requests, validates the CSRF token before any authentication logic acts on the request body. 3. **`UsernamePasswordAuthenticationFilter`** (and siblings like `BasicAuthenticationFilter`, `BearerTokenAuthenticationFilter`) — attempt authentication and, on success, store the `Authentication` in the context. 4. **`ExceptionTranslationFilter`** — wraps everything *after* it in a try/catch. It converts a thrown `AuthenticationException` into a 401 / redirect-to-login and an `AccessDeniedException` into a 403 (or login for anonymous users). 5. **`AuthorizationFilter`** (Spring Security 6; replaced `FilterSecurityInterceptor`) — the authorization decision point. It runs *last*, once identity is established, and throws `AccessDeniedException` if the caller lacks the required authority. Note the crucial pairing: **`ExceptionTranslationFilter` sits immediately before `AuthorizationFilter`** so it can catch the `AccessDeniedException` that `AuthorizationFilter` throws. A filter only catches exceptions from filters *after* it. ## Where the order comes from You do not assign numbers. Spring Security's **`FilterOrderRegistration`** hard-codes the relative order of every built-in filter, and the DSL (`HttpSecurity`) adds them in that order based on which features you enable. You influence order only by inserting *custom* filters relative to a known one via `addFilterBefore`, `addFilterAfter`, or `addFilterAt`. ## Gotchas - Enabling/disabling a feature (e.g. `.csrf().disable()`) removes that filter entirely — it doesn't just no-op. - Filters that short-circuit (send a response without calling `chain.doFilter`) stop the pipeline; nothing downstream runs. - The `SecurityContextHolder` is `ThreadLocal` by default, so the load-early/clear-late bracketing by `SecurityContextHolderFilter` prevents context leaking across pooled threads.

  • Is Spring Security one filter or many, from the servlet container's point of view?
    One: the container sees a single `springSecurityFilterChain` filter (`FilterChainProxy`). Internally that proxy delegates to an ordered list of Spring Security filters — the `SecurityFilterChain`.
  • How do filters pass 'who the user is' down the chain?
    Through `SecurityContextHolder`, a thread-local holding the `SecurityContext` and its `Authentication`. Authentication filters write it; `AuthorizationFilter` and your code read it.

saying these in an interview costs you the question

  • Thinking each SecurityFilterChain filter is registered separately with the servlet container (only FilterChainProxy is).
  • Believing you assign numeric order to Spring's built-in filters.
  • Saying authorization happens before authentication.

context

open as a page

Walk through the roles and relative order of SecurityContextHolderFilter, CsrfFilter, UsernamePasswordAuthenticationFilter, ExceptionTranslationFilter, and AuthorizationFilter.

level: middleimportance: must knowfreq 65%

basics

~10 s

Order: SecurityContextHolderFilter loads the current user; CsrfFilter checks the CSRF token; UsernamePasswordAuthenticationFilter authenticates form logins; ExceptionTranslationFilter catches security errors from below it; AuthorizationFilter (last) decides if the user may access the resource.

open as a page

How do addFilterBefore, addFilterAfter, and addFilterAt work, and when would you use each to insert a custom filter?

level: seniorimportance: should knowfreq 55%

basics

~20 s

They insert your custom filter relative to a known Spring Security filter. addFilterBefore(f, X) runs f before filter X; addFilterAfter(f, X) after X; addFilterAt(f, X) at X's position (order among same-position filters is undefined). Use them because you can't hand-number Spring's built-in filters.

open as a page

Why does CsrfFilter run before the authentication filters, and what breaks if you place a state-changing custom filter before it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

CsrfFilter runs early so a forged, state-changing request is rejected before any authentication or business logic acts on it. If a custom filter that mutates state runs before CsrfFilter, it executes on requests that CSRF validation would have blocked — reintroducing the CSRF vulnerability.

open as a page

When you define multiple SecurityFilterChain beans, how does FilterChainProxy decide which chain handles a request, and how does that interact with filter ordering?

level: principalimportance: should knowfreq 35%

basics

~20 s

FilterChainProxy tries each SecurityFilterChain in bean order and uses the FIRST one whose request matcher matches — only that chain's filters run. So a broad or unordered chain declared first can shadow a more specific one. Order the beans with @Order and give each a securityMatcher.

open as a page