Skip to content
Spring Boot sb security 3 min read

Configuring Security

In Spring Security 6 you configure everything through a SecurityFilterChain @Bean that you build with the HttpSecurity lambda DSL. The old WebSecurityConfigurerAdapter was removed — there is no base class to extend. This page shows the full bean, how to declare authorization rules with requestMatchers, how to enable or disable individual features, and how to run more than one chain.

The SecurityFilterChain bean

A @Configuration class declares a method that takes HttpSecurity, configures it with lambdas, and returns http.build().

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/css/**", "/js/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/orders/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated())
            .formLogin(form -> form
                .loginPage("/login").permitAll())
            .httpBasic(Customizer.withDefaults())
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/"));
        return http.build();
    }
}

Note: @EnableWebSecurity is optional when spring-boot-starter-security is present (Boot adds it for you), but declaring it makes intent explicit and is required if you set @EnableWebSecurity(debug = true).

authorizeHttpRequests and requestMatchers

authorizeHttpRequests declares the rules the AuthorizationFilter enforces. Rules are evaluated top to bottom, and the first match wins, so order from most specific to least specific and end with a catch-all.

.authorizeHttpRequests(auth -> auth
    .requestMatchers("/api/admin/**").hasRole("ADMIN")  // specific first
    .requestMatchers("/api/**").authenticated()
    .anyRequest().permitAll())                          // catch-all last

Common access rules:

RuleMeaning
permitAll()Anyone, authenticated or not
denyAll()Nobody
authenticated()Any logged-in user
hasRole("ADMIN")Authority ROLE_ADMIN
hasAnyRole("USER","ADMIN")Any of the listed roles
hasAuthority("orders:read")Exact authority string (no prefix added)
access(...)A custom AuthorizationManager for advanced logic

You can match by HTTP method and by path variables:

.requestMatchers(HttpMethod.POST, "/api/orders").hasRole("USER")
.requestMatchers(HttpMethod.DELETE, "/api/orders/{id}").hasRole("ADMIN")

Warning: Spring Security 6 fails fast if a request matches no rule, so always finish with anyRequest(). A missing catch-all throws on the first unmatched request.

Enabling and disabling features

Each DSL section toggles a feature. Pass Customizer.withDefaults() to keep defaults, a lambda to customize, or call .disable() to turn it off.

http
    .csrf(csrf -> csrf.disable())                 // common for stateless APIs
    .cors(Customizer.withDefaults())              // use the CorsConfigurationSource bean
    .formLogin(form -> form.disable())            // no HTML login for a pure API
    .httpBasic(Customizer.withDefaults())
    .sessionManagement(s -> s
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
    .headers(h -> h
        .frameOptions(frame -> frame.sameOrigin())); // e.g. for the H2 console

A typical stateless REST API configuration:

@Bean
public SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated());
    return http.build();
}

Multiple filter chains

Define more than one SecurityFilterChain bean to apply different rules to different URL spaces — for example a stateless API and a stateful admin UI. Use securityMatcher to scope each chain, and @Order to control which is checked first.

@Bean
@Order(1)
public SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/**")            // only requests under /api
        .csrf(csrf -> csrf.disable())
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain webChain(HttpSecurity http) throws Exception {
    http                                       // everything else
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/", "/login").permitAll()
            .anyRequest().authenticated())
        .formLogin(Customizer.withDefaults());
    return http.build();
}

Tip: FilterChainProxy uses the first matching chain only. Give the narrow securityMatcher chain the lower @Order so it is checked before the broad fallback chain.

Pitfalls

  • Forgetting anyRequest() — unmatched requests error out in Security 6.
  • Putting the catch-all rule before specific ones — earlier rules shadow later ones.
  • Disabling CSRF on a cookie/session app — only disable it for stateless token APIs (see CSRF).
  • Multiple chains without securityMatcher — only the first chain ever runs.
Last updated June 13, 2026
Was this helpful?