Spring Boot is the dominant framework for Java microservices, and it pulls in a massive dependency tree. A minimal Spring Boot web application starts with over 50 transitive dependencies. When you add Spring Security, Spring Data, and a few starters, you are easily past 200. Managing the security of that tree requires deliberate effort.
Spring Boot's Dependency Management
Spring Boot uses a Bill of Materials (BOM) to manage dependency versions. When you declare spring-boot-starter-parent as your parent POM, or import spring-boot-dependencies in dependency management, Spring Boot pins compatible versions for hundreds of libraries:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
This is a security advantage. The Spring team tests these versions together and patches them as a set. When a CVE hits a library in the BOM, the next Spring Boot release includes the fix.
The catch: Spring Boot releases happen on a schedule. If a critical CVE drops in a dependency between Spring Boot releases, you need to override the version yourself:
<properties>
<snakeyaml.version>2.2</snakeyaml.version>
</properties>
Actuator Security
Spring Boot Actuator exposes operational endpoints. In older Spring Boot versions, these were often publicly accessible by default. In Spring Boot 3.x, most actuator endpoints are secured, but misconfigurations are still common.
# application.yml
management:
endpoints:
web:
exposure:
include: health, info, metrics
endpoint:
health:
show-details: when-authorized
server:
port: 8081 # Separate port for actuator
Rules:
- Never expose all actuator endpoints (
include: "*") in production. - Run actuator on a separate port that is not internet-accessible.
- Protect actuator endpoints with Spring Security.
- Be especially careful with
env,configprops, andheapdumpendpoints. They can leak secrets.
@Configuration
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.anyRequest().hasRole("ACTUATOR_ADMIN")
);
return http.build();
}
}
Spring Security Configuration
Spring Security 6.x (Spring Boot 3.x) uses a builder-based configuration:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
.frameOptions(frame -> frame.deny())
.httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000))
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
}
Common Spring Security Mistakes
- Permit all by default. Always deny by default and explicitly allow specific paths.
- Disabling CSRF without understanding the implications. Only disable CSRF if you exclusively use token-based authentication.
- Using in-memory users in production. The
UserDetailsServicewith hardcoded credentials should never reach production. - Not configuring CORS properly.
allowedOrigins("*")is almost never correct.
Vulnerability Scanning
OWASP Dependency-Check
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.0.6</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
</configuration>
</plugin>
Use suppression files to handle false positives, not to hide real vulnerabilities:
<!-- dependency-check-suppressions.xml -->
<suppressions>
<suppress>
<notes>False positive: this CVE applies to a different product</notes>
<cve>CVE-2023-XXXXX</cve>
</suppress>
</suppressions>
Spring Boot's Built-in Checks
Spring Boot 3.x added dependency vulnerability checks. Check the Spring Boot release notes for security-related dependency updates with every release.
Production Hardening
Disable DevTools
Spring Boot DevTools should never be in production:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
The optional flag prevents DevTools from being included in the packaged JAR. Double-check your fat JAR to confirm.
Error Handling
Do not expose stack traces in production:
server:
error:
include-stacktrace: never
include-message: never
Logging
Sanitize sensitive data from logs. Spring Boot's logging can inadvertently log request parameters, headers, and payloads that contain credentials.
SBOM Generation
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.7.10</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>makeAggregateBom</goal></goals>
</execution>
</executions>
</plugin>
How Safeguard.sh Helps
Safeguard.sh integrates with your Spring Boot CI pipelines to ingest CycloneDX SBOMs and provide continuous dependency monitoring. It tracks which Spring Boot version each of your services runs, flags services on unsupported versions, and correlates your dependency tree against vulnerability feeds. When the next critical CVE hits a Spring ecosystem library, Safeguard.sh identifies every affected service across your organization within minutes, not days.