Chaturmind
LearnDSASystem DesignDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Spring Security & JWT Auth

Spring Security Basics

  • Spring Security Overview
  • JWT Authentication
  • Authentication Mechanics

Authorization

  • Role-Based Access Control
  • Password Encoding
  • OAuth2 & Social Login Basics
Chaturmind
← Spring Security & JWT Auth

Spring Security Basics

  • Spring Security Overview
  • JWT Authentication
  • Authentication Mechanics

Authorization

  • Role-Based Access Control
  • Password Encoding
  • OAuth2 & Social Login Basics
HomeLearnSpring BootSpring Security & JWT AuthSpring Security Basics
✓ FreeIntermediate· 10 min read

Spring Security Overview

Filter chains, SecurityContext, authentication vs authorization — how Spring Security is wired.

Published September 21, 2026


Spring Security Overview

Spring Security is the standard security framework for Spring Boot applications. It handles authentication (who are you?) and authorization (what are you allowed to do?) through a chain of servlet filters.

The Security Filter Chain

Every HTTP request passes through a chain of filters before reaching your controller:

HTTP Request
    ↓
SecurityContextPersistenceFilter  (load/save SecurityContext)
    ↓
UsernamePasswordAuthenticationFilter  (form login)
    ↓
BearerTokenAuthenticationFilter  (JWT/OAuth2)
    ↓
ExceptionTranslationFilter  (handle 401/403)
    ↓
AuthorizationFilter  (check permissions)
    ↓
Your Controller

DelegatingFilterProxy — bridging servlet filters to Spring beans

Servlet containers (Tomcat) manage filters through their own lifecycle, entirely separate from Spring's IoC container — DelegatingFilterProxy is the bridge: a thin servlet Filter registered with the container that does nothing itself except look up a real, Spring-managed bean (FilterChainProxy, which wraps the actual security filter chain) and delegate every call to it. This is what lets Spring Security's filters be ordinary Spring beans — dependency-injected, configured via @Bean methods — while still participating correctly in the servlet container's own filter lifecycle.

Multiple SecurityFilterChains, matched by request pattern

@Bean
@Order(1)
SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
    return http.securityMatcher("/api/**")
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .build();
}

@Bean
@Order(2)
SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
    return http.securityMatcher("/public/**")
        .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
        .build();
}

A single application can register several SecurityFilterChain beans, each scoped to a different securityMatcher pattern — the first chain whose pattern matches the incoming request handles it entirely. This lets /api/** require authentication while /public/** stays open, without either configuration interfering with the other.

Filter ordering and short-circuiting

Filters run in a strict, fixed order (SecurityContextPersistenceFilter → CsrfFilter → UsernamePasswordAuthenticationFilter → ... → AuthorizationFilter). Each filter can either call chain.doFilter() to pass the request to the next filter (pass through), or write a response directly and never call doFilter() (short-circuit) — this is Chain of Responsibility applied directly (see Chain of Responsibility), and it's exactly why filter order matters: a filter earlier in the chain can reject a request before any later filter (including the actual authorization check) ever runs.

ExceptionTranslationFilter

Sits specifically to catch AuthenticationException and AccessDeniedException thrown by later filters, translating them into the correct HTTP response — an unauthenticated request attempting a protected resource becomes a 401, an authenticated-but-unauthorized request becomes a 403. Without this filter, those exceptions would otherwise propagate as generic 500 errors.

FilterSecurityInterceptor / AuthorizationFilter — the final access decision

The last filter in the standard chain, making the actual "is this request allowed" decision based on the configured authorization rules — everything before it (authentication filters, CSRF, exception translation) exists to get a correctly-populated Authentication object to this final gate.

Servlet Filter vs Spring HandlerInterceptor

A Filter operates at the servlet container level, before the request even reaches Spring MVC's DispatcherServlet — it has no knowledge of which controller method will eventually handle the request. A Spring HandlerInterceptor operates inside Spring MVC, after the DispatcherServlet has resolved which handler (controller method) will process the request, giving it access to the handler itself and the resulting ModelAndView. Security enforcement belongs at the Filter level specifically because it should reject unauthorized requests before any MVC-layer processing (handler resolution, argument binding) happens at all.

CORS vs CSRF — distinct, often confused

CORS (Cross-Origin Resource Sharing) is a browser-enforced mechanism letting a server explicitly declare which origins are allowed to call it, via Access-Control-Allow-Origin and related response headers — it protects against a malicious site's JavaScript reading responses from an API the browser has credentials for. CSRF (Cross-Site Request Forgery) is about forged requests from an already-trusted origin (a malicious site tricking a logged-in user's browser into submitting a request the user never intended) — different threat model entirely, addressed with anti-forgery tokens, not origin headers.

API security checklist

Authentication and authorization enforced on every endpoint (no accidentally-open routes), input validation and output encoding, rate limiting (see the Rate Limiter case study), secrets never committed to source control, and least-privilege scoping for service-to-service calls — a practical baseline worth checking explicitly on any new API surface, not just relying on framework defaults to catch every gap.

Basic Security Configuration (Spring Boot 3)

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())  // disable for REST APIs
            .sessionManagement(sm ->
                sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**").permitAll()  // public endpoints
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()  // everything else needs auth
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }

    @Bean
    public AuthenticationManager authManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

UserDetailsService

Spring Security loads user details through UserDetailsService:

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email)
            throws UsernameNotFoundException {
        return userRepository.findByEmail(email)
            .map(user -> User.builder()
                .username(user.getEmail())
                .password(user.getPasswordHash())
                .roles(user.getRoles().toArray(new String[0]))
                .accountExpired(!user.isActive())
                .build())
            .orElseThrow(() ->
                new UsernameNotFoundException("User not found: " + email));
    }
}

SecurityContextHolder — accessing the current user

// Get the current authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();

// In a controller — inject directly
@GetMapping("/me")
public UserDto getMe(@AuthenticationPrincipal UserDetails user) {
    return userService.findByEmail(user.getUsername());
}

Method-Level Security

@Configuration
@EnableMethodSecurity  // enables @PreAuthorize, @PostAuthorize
public class MethodSecurityConfig {}

@Service
public class AdminService {

    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() { ... }

    @PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
    public User getUser(String userId) { ... }

    @PostAuthorize("returnObject.ownerId == authentication.principal.id")
    public Document getDocument(String docId) { ... }
}

Interview Tips

  1. Explain the difference between authentication (verifying identity) and authorization (verifying permissions).
  2. STATELESS session management means no HttpSession is created — every request must carry credentials (JWT).
  3. @PreAuthorize uses Spring Expression Language (SpEL) — you can access the authentication object and method arguments.

Next

JWT Authentication

AI Tutor

Lesson: Spring Security Overview

Quick actions

AI responses can be inaccurate. Verify critical information.