Securing APIs with Spring Security

Spring Boot · Advanced · 18 min

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(...) or oauth2ResourceServer(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/shop

Remember

  • Adding the starter secures everything; then you open up deliberately
  • Configure a SecurityFilterChain bean 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.

Check yourself

  1. Rules are .anyRequest().authenticated() followed by .requestMatchers("/public/**").permitAll(). What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.spring.io/spring-boot/reference/web/spring-security.html