Every real API needs authentication and authorization, and Spring Security is the standard.
Adding spring-boot-starter-security secures everything immediately: all endpoints require authentication, with a generated password logged at startup and HTTP Basic plus a login form. That's a safe default; you then define what you actually want.
Security is a chain of servlet filters in front of the DispatcherServlet. Configure it by declaring a SecurityFilterChain bean using the lambda DSL on HttpSecurity:
authorizeHttpRequests(auth -> auth.requestMatchers("/public/**").permitAll().requestMatchers(HttpMethod.DELETE).hasRole("ADMIN").anyRequest().authenticated())defines URL rules, evaluated in order.httpBasic(Customizer.withDefaults()),formLogin(...)oroauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))choose how users authenticate.- For stateless token-based APIs, disable sessions (
SessionCreationPolicy.STATELESS); CSRF protection can then be disabled because no cookies authenticate requests.
With a JWT resource server (spring-boot-starter-oauth2-resource-server), set spring.security.oauth2.resourceserver.jwt.issuer-uri and Spring validates tokens from your identity provider (Keycloak, Auth0, Entra ID) on every request.
Method-level rules use @EnableMethodSecurity and @PreAuthorize("hasRole('ADMIN') or #id == authentication.name"). Inject the current user with @AuthenticationPrincipal. Store passwords only with a PasswordEncoder such as BCrypt. Test with @WithMockUser or jwt() request post-processors.
Example
@Configuration
@EnableMethodSecurity
class SecurityConfig {
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/api/public/**").permitAll()
.requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable()) // stateless bearer-token API
.build();
}
}
@RestController
class MeController {
@GetMapping("/api/me")
Map<String, Object> me(@AuthenticationPrincipal Jwt jwt) {
return Map.of("sub", jwt.getSubject(), "scopes", jwt.getClaimAsString("scope"));
}
}
// application.properties:
// spring.security.oauth2.resourceserver.jwt.issuer-uri=https://auth.example.com/realms/shopRemember
- Adding the starter secures everything; then you open up deliberately
- Configure a
SecurityFilterChainbean with the lambda DSL - Request matchers are evaluated in order; put specific rules first
- JWT resource servers validate bearer tokens from an issuer
Watch out for
- Disabling CSRF on an application that authenticates with session cookies, opening it to cross-site request forgery.