SecurityFilterChain and what defining one turns off
WebSecurityConfigurerAdapter was deprecated in Spring Security 5.7.0-M2 and
removed in 6.0. The class does not exist, so this is not a style preference —
code extending it does not compile. Configuration is a SecurityFilterChain bean.
That much is widely known by now. The part that is not, and the reason this page sits at the top of the checklist, is what defining that bean does to the rest of your application.
Defining a SecurityFilterChain turns off Actuator’s automatic protection
Section titled “Defining a SecurityFilterChain turns off Actuator’s automatic protection”From Spring Boot’s own Actuator documentation:
If Spring Security is on the classpath and no other
SecurityFilterChainbean is present, all actuators other than/healthare secured by Spring Boot auto-configuration. If you define a customSecurityFilterChainbean, Spring Boot auto-configuration backs off and lets you fully control the actuator access rules.
Read those two sentences together with the migration above. Every application moving
off WebSecurityConfigurerAdapter must define a SecurityFilterChain, and the
moment it does, Boot stops protecting the Actuator endpoints on its behalf.
Nothing warns. The build succeeds, the login page works, the tests pass — and
/actuator/** is now governed by whatever your new chain says, which for a chain
written from a tutorial about form login is usually nothing at all.
This is the single most valuable thing on this page: a security migration that silently widens a different attack surface.
@BeanSecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth // Say something explicit about actuator. Boot no longer will. .requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll() .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN") .requestMatchers("/", "/login").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); return http.build();}EndpointRequest is Boot’s own matcher for this — it resolves the actual Actuator
paths rather than hardcoding /actuator/**, which matters because
management.endpoints.web.base-path can move them.
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/actuator/envAnything other than 401, 403 or 404 from an unauthenticated request means the
chain does not cover it. Test it against a running application rather than reading the
config — this is precisely the case where the config looks complete.
The renames that came with it
Section titled “The renames that came with it”Two method names changed at the same time, and automated migrations handle them:
authorizeRequests()→authorizeHttpRequests()antMatchers()→requestMatchers()
The rename that matters is the first one, because the two methods do not use the same matching engine, and a migration tool will happily produce code that compiles while matching different paths than it used to. Read every security-relevant line a migration tool touches. Compiling is not the bar; the bar is that the rules still say what you meant.
Where you actually are
Section titled “Where you actually are”Spring Boot 4.1.0 manages Spring Security 7.1.0. Spring Security 6.5 was the last 6.x release, and its own migration guide recommends going to 6.5 first, using its preparation steps, then moving to 7 — rather than jumping.
So WebSecurityConfigurerAdapter advice is now two majors stale. Anything written
against the adapter predates 2022 and should be treated as describing a different
framework.
This is the change most likely to lock people out of their own application, in both directions: too permissive and everything is public, too strict and the login page itself is behind authentication.
.anyRequest().authenticated() without a permitAll() for /login and static assets
produces a redirect loop. Order matters — the first matching rule wins, so a broad
matcher above a narrow one silently swallows it.
Migrate on a branch with an integration test that asserts an anonymous request to a protected path gets 401 and to a public path gets 200. That test is worth more than reading the chain.
Related
Section titled “Related”- Actuator exposure — what you just took responsibility for
- Actuator exposed to the internet — where that ends up
- CSRF configuration — the other thing tutorials switch off in this bean