RizTech Academy logo
RizTech Academy
SecurityLesson 1 of 535 min

Spring Security: the filter chain

Spring Security is powerful and, to newcomers, baffling — you add the dependency and suddenly every endpoint demands a login, configured by mechanisms you cannot see. This module's job is to make it legible. It starts here with the one idea everything rests on: Spring Security is a chain of filters that every request passes through before reaching your controller. Understand the filter chain and the rest of Spring Security stops being magic. Verified against a running Dakiya.

Security is a filter chain in front of your app

Every HTTP request to a Spring app passes through a filter chain before it reaches your controller. Spring Security inserts its own filters into that chain — a series of steps that, in order, work out who the caller is (authentication) and whether they may do what they asked (authorisation), rejecting the request if either fails. Your controller only runs if the request made it through the security filters.

The mental model: Spring Security is a gate in front of your controllers, built as an ordered chain of filters. Each filter has one job — one reads the Authorization header, one loads the user, one checks authorisation, one handles a login form, and so on. When you "configure Spring Security", you are configuring this chain: which paths need authentication, how users are authenticated, what happens on failure.

Why every endpoint is locked by default

Add spring-boot-starter-security and, with no configuration, every endpoint requires authentication — Boot auto-configures a filter chain that locks everything down and generates a random password (printed at startup). This surprises everyone, and it is deliberate and correct: secure by default, so you cannot forget to protect an endpoint — you have to consciously open the ones that should be public. A framework that started open and required you to remember to lock each endpoint would ship holes; Spring Security starts closed. So the "why is my endpoint asking for a password?" moment is the framework protecting you, not a bug.

Configuring the chain: SecurityFilterChain

You replace the default by defining a SecurityFilterChain bean (the "your config wins" rule). Dakiya's:

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(c -> c.disable())                          // see the note below
            .authorizeHttpRequests(a -> a
                .requestMatchers("/ping").permitAll()        // public
                .requestMatchers("/api/**").authenticated()  // require login
                .anyRequest().permitAll())
            .httpBasic(Customizer.withDefaults());           // authenticate via HTTP Basic
        return http.build();
    }
}

authorizeHttpRequests is the heart: a set of rules, matched top to bottom, saying which paths are permitAll() (public), authenticated() (require a logged-in user), or hasRole(...) (require a role — next lessons). Order matters — the first matching rule wins, so put specific rules before general ones. Verified against the running app: /ping returned 200 to anyone (permitAll), while /api/parcels returned 401 to an anonymous request and 200 with valid credentials. The chain did exactly what the rules said.

Authentication: who are you?

Authentication establishes the caller's identity. Spring Security supports several mechanisms, chosen in the chain:

  • HTTP Basic (httpBasic) — credentials in the Authorization header. Simple, fine for APIs and testing (Dakiya uses it here; verified: staff:staff-pass-123 → 200, wrong password → 401).
  • Form login (formLogin) — a login page and session cookie, for server-rendered web apps.
  • Token / JWT — a token in the header, for stateless APIs (the JWT lesson) — the common choice for a real REST API consumed by a mobile app or SPA.

Users come from a UserDetailsService. For learning, an in-memory user works; in production it is backed by your database (a users table). Whatever the source, authentication ends with a SecurityContext holding the authenticated principal, available to the rest of the request.

A note on CSRF and stateless APIs

You will notice csrf(c -> c.disable()) above, and it deserves a precise explanation (the common-mistakes lesson returns to it). CSRF protection matters for browser sessions with cookies — it stops another site forging a state-changing request using your session cookie. A stateless REST API authenticated by a token (not a cookie) is not vulnerable to CSRF in the same way, because there is no ambient cookie for an attacker to ride — so disabling CSRF is appropriate there. But disabling it on a cookie/session-based app re-opens the hole. The rule: CSRF off is correct for a token-authenticated stateless API; CSRF must stay on for a session-cookie web app. Know which you are building — do not disable CSRF by reflex because a tutorial did.

Check your work

The filter chain. Every request passes through Spring Security's ordered filters before reaching the controller; they establish identity (authentication) and check permission (authorisation), rejecting on failure. Configuring security = configuring this chain.

Locked by default. Adding the starter locks every endpoint (generated password) — deliberate: secure by default, so you consciously open public endpoints rather than forgetting to lock ones.

SecurityFilterChain. A bean with authorizeHttpRequests rules (permitAll/authenticated/hasRole), matched top to bottom (order matters), plus an auth mechanism. Verified: /ping 200 public, /api/** 401 anon / 200 authed.

Authentication mechanisms. HTTP Basic (header creds — verified 200/401), form login (page + session cookie), token/JWT (stateless APIs). Users from a UserDetailsService (in-memory for learning, DB in production); ends with a SecurityContext holding the principal.

CSRF. Off is correct for a token-authenticated stateless API (no cookie to ride); must stay on for a session-cookie web app. Know which you have.

Practice

  1. Add spring-boot-starter-security with no config; hit any endpoint and confirm it demands a login (read the generated password in the log).
  2. Add a SecurityFilterChain making /ping public and /api/** authenticated; confirm anon /api → 401, authed → 200, /ping → 200 (reproduce the verified results).
  3. Reorder the authorizeHttpRequests rules so a general rule precedes a specific one and observe the specific one never applies — order matters.
  4. Authenticate with HTTP Basic using correct and wrong credentials; confirm 200 vs 401.
  5. Reason about whether Dakiya (a token API) or a session-cookie web app should disable CSRF, and why.
  6. Trace, in words, the path a request takes through the filter chain from arrival to your controller.

Official documentation

Next: storing passwords correctly.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship