Databases, Spring and microservices ยท 4. Spring Security, lesson 2 of 8

Inside Spring Security: DelegatingFilterProxy, SecurityFilterChain and the SecurityContext

Intermediate3 min read@since 17Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

How a request gets secured:

  1. The servlet container runs its filters. Spring Boot registers one called DelegatingFilterProxy, which hands the request to Spring's FilterChainProxy.
  2. 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.
  3. The chosen chain runs its filters in a fixed order: SecurityContextHolderFilter, HeaderWriterFilter, CorsFilter, CsrfFilter, LogoutFilter, the authentication filters, AnonymousAuthenticationFilter, ExceptionTranslationFilter and finally AuthorizationFilter.
  4. Authentication filters store the logged-in user in the SecurityContextHolder (per request, per thread). Your code reads it from there.
  5. 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.

Security filter chain lab

Example

Java
@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();
    }
}
Reading the current user
@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

Part of Spring Core and Spring Security in depth.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.