Interview Prep Zoneby CuriouserLabs

Question 1 of 5 · senior level

Explain Spring Security's architecture — the filter chain, SecurityContext, and how a request gets authenticated.

🎤 Say this first

Spring Security is a chain of servlet filters installed before the DispatcherServlet (via DelegatingFilterProxy → FilterChainProxy). Each SecurityFilterChain bean matches certain requests and runs ordered filters: authentication filters extract credentials and call an AuthenticationManager (→ AuthenticationProviders → e.g. UserDetailsService), and on success store the Authentication in the SecurityContextHolder (ThreadLocal). Authorization happens later per-request (authorizeHttpRequests) and per-method (@PreAuthorize). Failures route to the AuthenticationEntryPoint (401) or AccessDeniedHandler (403). It's all beans and filters — inspectable and replaceable.

The full picture

  • Order matters and is documented: CORS → CSRF → auth filters (Basic/Bearer/form) → exception translation → authorization (last). Custom filters slot in with addFilterBefore/After — knowing where is the senior part (e.g., a tenant filter after authentication, before authorization).
  • Multiple chains: one SecurityFilterChain for /api/** (stateless, JWT) and another for /admin/** (session, form login) — securityMatcher picks per request; the first matching chain wins.
  • ThreadLocal implications: SecurityContextHolder context doesn't follow you to @Async threads (propagate via DelegatingSecurityContextExecutor) — same class of bug as transaction ThreadLocals.
  • Boot's contribution: sensible default chain (everything authenticated, login form, generated password) that backs off the moment you define your own SecurityFilterChain — same conditional model as all auto-config.
A modern (Boot 3 / Security 6) configuration
@Configuration
@EnableWebSecurity
@EnableMethodSecurity                 // @PreAuthorize et al.
class SecurityConfig {

    @Bean
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        return http
            .securityMatcher("/api/**")
            .csrf(CsrfConfigurer::disable)                    // stateless API
            .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
            .authorizeHttpRequests(a -> a
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .oauth2ResourceServer(o -> o.jwt(withDefaults())) // Bearer JWT
            .build();
    }
}

🔄 Likely follow-up questions

  • Why does a filter-thrown AuthenticationException not hit your @ControllerAdvice?
  • What's the difference between AuthenticationManager, Provider and UserDetailsService?
  • How do you add a custom authentication mechanism (e.g., HMAC-signed requests)?