Spring Security Introduction

What Spring Security handles, the security filter chain, and a modern SecurityFilterChain bean example.

What Spring Security handles

Spring Security is a framework for two related but distinct concerns:

  • Authentication — verifying who is making a request (a valid username/password, a valid token, a valid client certificate).
  • Authorization — deciding what an authenticated (or anonymous) request is allowed to do (can this user view this resource? can they delete it?).

Both are implemented as a chain of servlet filters that every request passes through before it ever reaches your controller code — meaning security decisions happen consistently, in one place, rather than being re-implemented inside every endpoint.

The security filter chain

Spring Security inserts itself into the servlet request pipeline as a series of filters, each responsible for one concern, evaluated in a fixed order for every incoming request:

Plaintext
Request
  │
  v
┌──────────────────────────┐
│  SecurityContextFilter     │  loads/stores the current Authentication
├──────────────────────────┤
│  Authentication filter(s)  │  e.g. checks a login form, or validates a JWT
├──────────────────────────┤
│  Authorization filter      │  checks the authenticated principal against access rules
├──────────────────────────┤
│  Your controller           │  only reached if authorization passes
└──────────────────────────┘
  │
  v
Response (or 401/403 short-circuit before ever reaching the controller)

If any filter rejects the request — no valid credentials, or a valid principal without permission — the chain short-circuits with 401 Unauthorized or 403 Forbidden and the controller never runs.

Configuring the chain: SecurityFilterChain

Since Spring Security 6 (Spring Boot 3+), security rules are configured as a SecurityFilterChain bean — a plain @Bean method, not a subclassed configuration class:

Java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .csrf(csrf -> csrf.disable()); // acceptable for a stateless JSON API; keep enabled for form-based apps

        return http.build();
    }
}
  • .authorizeHttpRequests(...) declares which URL patterns require what — evaluated top to bottom, first match wins, so more specific rules must come before more general ones (/api/admin/** before the catch-all anyRequest()).
  • .formLogin(...) enables Spring Security's built-in login form (fine for a server-rendered app; a stateless API typically replaces this with token-based authentication instead — covered later in this track).
  • .requestMatchers("/api/public/**").permitAll() explicitly allows unauthenticated access to a specific path pattern.

A note on legacy code: older Spring Security tutorials configure security by extending WebSecurityConfigurerAdapter and overriding configure(HttpSecurity http). That class was deprecated in Spring Security 5.7 and removed entirely in Spring Security 6 — if you see it in an example, it's for a version prior to what ships with current Spring Boot 3.x, and won't compile against it. The SecurityFilterChain @Bean approach shown above is the only supported style going forward.

Common mistakes

  • Copying WebSecurityConfigurerAdapter-based examples into a current Spring Boot 3.x project — the class doesn't exist anymore, and the fix is to express the same rules as a SecurityFilterChain bean.
  • Ordering authorizeHttpRequests rules so that a broad pattern (anyRequest().authenticated()) appears before a more specific exception (/api/public/**) — since matching is first-match-wins, the broad rule shadows the specific one and the "public" path never actually becomes public.
  • Disabling CSRF protection reflexively on every project without considering whether the application is actually stateless — CSRF matters for cookie/session-based browser apps and should stay enabled there; it's safe to disable only for genuinely stateless, token-authenticated APIs with no session cookies involved.
  • Assuming Spring Security is "just for login forms" — its authorization model applies equally to JSON APIs, service-to-service calls, and method-level checks, independent of how authentication itself happens.