Inside Spring Security: DelegatingFilterProxy, SecurityFilterChain and the SecurityContext
How a request gets secured:
- The servlet container runs its filters. Spring Boot registers one called DelegatingFilterProxy, which hands the request to Spring's FilterChainProxy.
- FilterChainProxy picks the first SecurityFilterChain whose matcher matches the request. You can define several, for example one for
/api/**with JWT and one for the rest with form login, ordered with@Order. - The chosen chain runs its filters in a fixed order: SecurityContextHolderFilter, HeaderWriterFilter, CorsFilter, CsrfFilter, LogoutFilter, the authentication filters, AnonymousAuthenticationFilter, ExceptionTranslationFilter and finally AuthorizationFilter.
- Authentication filters store the logged-in user in the SecurityContextHolder (per request, per thread). Your code reads it from there.
- ExceptionTranslationFilter turns security exceptions into responses: not logged in โ 401 via the AuthenticationEntryPoint; logged in but not allowed โ 403 via the AccessDeniedHandler.
The lab below sends real-world requests through this chain.
Example
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
@Order(1)
SecurityFilterChain api(HttpSecurity http) throws Exception {
return http
.securityMatcher("/api/**") // this chain only handles /api/**
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())) // Bearer JWT
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable()) // tokens in headers, not cookies
.build();
}
@Bean
@Order(2)
SecurityFilterChain web(HttpSecurity http) throws Exception {
return http // everything else: a classic web app
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/css/**").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.build();
}
}@GetMapping("/api/me")
Profile me(@AuthenticationPrincipal Jwt jwt) { // injected from the SecurityContext
return profiles.findByEmail(jwt.getSubject());
}
// Anywhere in the same request thread:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();Common mistake
Disabling CSRF on a chain that authenticates with session cookies, because a tutorial for token-based APIs did it.
Under the hood
Set logging.level.org.springframework.security=TRACE to see, for every request, which chain was chosen and what each filter did. The SecurityContext lives in a ThreadLocal, so it doesn't follow your work onto other threads: use DelegatingSecurityContextExecutor (or Spring's context propagation) for @Async tasks that need the user.
Check yourself
A logged-in user without the required role calls an endpoint. What is the response?
How this connects
Know these first
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.