Spring Security 7 implementation for Spring Boot 4. Use when configuring authentication, authorization, OAuth2/JWT resource servers, method security, or CORS/CSRF. Covers the mandatory Lambda DSL migration, SecurityFilterChain patterns, @PreAuthorize, and password encoding. For testing secured endpoints, see spring-boot-testing skill.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
references/authentication.mdreferences/jwt-oauth2.mdreferences/security-config.mdImplements authentication and authorization with Spring Security 7's mandatory Lambda DSL.
| Removed API | Replacement | Status |
|---|---|---|
and() method | Lambda DSL closures | Required |
authorizeRequests() | authorizeHttpRequests() | Required |
antMatchers() | requestMatchers() | Required |
WebSecurityConfigurerAdapter | SecurityFilterChain bean | Required |
@EnableGlobalMethodSecurity | @EnableMethodSecurity | Required |
authorizeHttpRequests() with requestMatchers()@EnableMethodSecurity + @PreAuthorize@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/actuator/health").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable()); // Stateless API
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize("/public/**", permitAll)
authorize("/api/admin/**", hasRole("ADMIN"))
authorize(anyRequest, authenticated)
}
oauth2ResourceServer { jwt { } }
sessionManagement { sessionCreationPolicy = SessionCreationPolicy.STATELESS }
csrf { disable() }
}
return http.build()
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?error=true")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
)
.sessionManagement(session -> session
.maximumSessions(1)
.expiredUrl("/login?expired=true")
)
.csrf(csrf -> csrf.csrfTokenRepository(
CookieCsrfTokenRepository.withHttpOnlyFalse() // For SPA
));
return http.build();
}
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig {}
@Service
public class OrderService {
@PreAuthorize("hasRole('ADMIN') or #customerId == authentication.principal.id")
public Order getOrder(Long customerId, Long orderId) {
// Admin or owner can access
}
@PreAuthorize("@orderSecurity.canModify(authentication, #orderId)")
public void updateOrder(Long orderId, OrderRequest request) {
// Delegates to security bean
}
@PostFilter("filterObject.isPublic or filterObject.ownerId == authentication.name")
public List<Order> findAll() {
// Filters results after execution
}
}
@Component("orderSecurity")
public class OrderSecurityEvaluator {
public boolean canModify(Authentication auth, Long orderId) {
// Custom authorization logic
return orderRepository.findById(orderId)
.map(order -> order.getOwnerId().equals(auth.getName()))
.orElse(false);
}
}
@Bean
public PasswordEncoder passwordEncoder() {
return Argon2PasswordEncoder.defaultsForSpring7();
}
.csrf(csrf -> csrf.csrfTokenRepository(
CookieCsrfTokenRepository.withHttpOnlyFalse()
))
// Or for stateless APIs with JWT:
.csrf(csrf -> csrf.disable())
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://frontend.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Requested-With"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
// In SecurityFilterChain:
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
| Anti-Pattern | Fix |
|---|---|
Using and() chaining | Use Lambda DSL closures |
antMatchers() | Replace with requestMatchers() |
authorizeRequests() | Replace with authorizeHttpRequests() |
| CSRF disabled without JWT | Keep CSRF for session-based auth |
| Hardcoded credentials | Use environment variables or Secret Manager |
permitAll() on sensitive endpoints | Audit all permit rules |
Missing authenticated() default | End with .anyRequest().authenticated() |
and() chaining in Security 7requestMatchers before general ones@EnableMethodSecurity@WithMockUser and JWT test support (see spring-boot-testing)