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 theAuthorizationheader. 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
- Add
spring-boot-starter-securitywith no config; hit any endpoint and confirm it demands a login (read the generated password in the log). - Add a
SecurityFilterChainmaking/pingpublic and/api/**authenticated; confirm anon/api→ 401, authed → 200,/ping→ 200 (reproduce the verified results). - Reorder the
authorizeHttpRequestsrules so a general rule precedes a specific one and observe the specific one never applies — order matters. - Authenticate with HTTP Basic using correct and wrong credentials; confirm 200 vs 401.
- Reason about whether Dakiya (a token API) or a session-cookie web app should disable CSRF, and why.
- Trace, in words, the path a request takes through the filter chain from arrival to your controller.
Official documentation
- Spring Security — Architecture (the filter chain) — How the filters work.
- Spring Security — Authorize HTTP requests —
authorizeHttpRequests, matchers. - Spring Security — CSRF — When it matters and when to disable it.
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