Architecture and Filter Chain Questions

Explain how a request flows through Spring Security.

HTTP request
  -> Servlet container filters
  -> DelegatingFilterProxy            (a plain servlet Filter registered by Boot)
  -> FilterChainProxy                 (bean "springSecurityFilterChain")
  -> picks the first matching SecurityFilterChain
       SecurityContextHolderFilter -> CorsFilter -> CsrfFilter -> LogoutFilter
       -> UsernamePasswordAuthenticationFilter / BearerTokenAuthenticationFilter
       -> ... -> ExceptionTranslationFilter -> AuthorizationFilter
  -> DispatcherServlet -> your @RestController

Security runs before the controller, as servlet filters, so an unauthenticated request never reaches your code.

What is DelegatingFilterProxy and why is it needed?

The servlet container creates filters itself and knows nothing about Spring beans. DelegatingFilterProxy is a normal servlet filter that looks up a Spring bean (springSecurityFilterChain, a FilterChainProxy) and delegates to it. That way the security filters are Spring beans that can use dependency injection.

How do you configure security in Spring Security 6?

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())                    // stateless JWT API
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

Tip: Rules are checked top to bottom and the first match wins, so put specific paths before general ones and anyRequest() last.

What are AuthenticationManager, ProviderManager and AuthenticationProvider?

  • AuthenticationManager: the interface with one method, authenticate(Authentication).
  • ProviderManager: the usual implementation; it asks a list of AuthenticationProviders in turn until one supports the token type.
  • AuthenticationProvider: does the real check. DaoAuthenticationProvider loads the user with UserDetailsService and compares passwords with PasswordEncoder; JwtAuthenticationProvider validates a JWT.
  • On success it returns an authenticated Authentication; on failure it throws an AuthenticationException.

What is the SecurityContextHolder and how does it store the user?

SecurityContextHolder holds the SecurityContext (which holds the Authentication) for the current request. The default strategy is MODE_THREADLOCAL, so each request thread sees its own user. That is also why the user is "lost" in @Async methods or new threads: wrap the executor with DelegatingSecurityContextAsyncTaskExecutor or use MODE_INHERITABLETHREADLOCAL. Between requests the context is stored by a SecurityContextRepository (HTTP session by default; nothing for stateless APIs).

Can you have more than one SecurityFilterChain? When would you?

@Bean @Order(1)
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
    http.securityMatcher("/api/**")
        .authorizeHttpRequests(a -> a.anyRequest().authenticated())
        .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    return http.build();
}

@Bean @Order(2)
SecurityFilterChain webChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
        .formLogin(Customizer.withDefaults());
    return http.build();
}

Yes. FilterChainProxy uses the first chain whose securityMatcher matches. A common case: stateless JWT for /api/** and session-based form login for an admin web UI.