Course topics

By WebNest Studio

Spring Boot Tutorial

Security Hardening Checklist for Spring Boot

Configuring authentication and authorization is only part of securing an application. Production incidents are just as often caused by an exposed Actuator endpoint, a verbose error page, a leaked secret, an outdated dependency or a missing rate limit.

This lesson collects the practical hardening steps experienced Spring Boot teams apply before going live, organised as a checklist you can run through for every service. Each item includes the configuration or code needed to implement it.

Transport and Headers

Serve everything over HTTPS, usually terminated at a load balancer. Set server.forward-headers-strategy=framework so Spring knows the original request was HTTPS, enable HSTS, and add a Content Security Policy for server-rendered pages. Spring Security already sends X-Content-Type-Options, X-Frame-Options and cache-control headers by default — do not disable them.

Secrets Management

Never commit passwords, API keys or signing keys. Read them from environment variables, Kubernetes secrets, or a secret manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) via spring.config.import. Rotate secrets regularly and immediately after anyone leaves the team or a leak is suspected. Add a secret scanner (GitHub secret scanning, gitleaks) to CI.

Actuator and Error Exposure

Expose only the Actuator endpoints you need (typically health, info, prometheus), preferably on a separate management port that is not reachable from the internet, and require authentication for the rest. Never expose env, heapdump or configprops publicly — they can reveal secrets. Keep server.error.include-stacktrace=never and return ProblemDetail responses without internal details.

Input Handling

Validate every request with Bean Validation, use parameterised queries (Spring Data and JdbcClient do this for you — never concatenate SQL), limit upload sizes, and encode output in templates (Thymeleaf escapes by default; avoid th:utext with user data). For outbound HTTP calls to user-supplied URLs, block internal addresses to prevent SSRF; Spring Boot 4.1 adds an InetAddressFilter for its HTTP clients.

Abuse Protection

Login, registration, password reset and OTT endpoints need rate limiting to stop credential stuffing and enumeration. Use a gateway or a library such as Bucket4j, lock or slow down accounts after repeated failures, and log authentication events. Spring Security publishes AuthenticationSuccessEvent and AbstractAuthenticationFailureEvent, which you can listen to for auditing.

Dependencies and Supply Chain

Most vulnerabilities in Java applications come from dependencies. Stay on a supported Spring Boot line, update patch versions promptly, and scan dependencies in CI with OWASP Dependency-Check, Snyk, GitHub Dependabot or similar. Generate an SBOM (Spring Boot exposes one via the sbom Actuator endpoint when you use the CycloneDX plugin) so you can quickly answer "are we affected?" when a new CVE is announced.

Examples

Production-oriented application.yml

Java
server:
  forward-headers-strategy: framework
  error:
    include-stacktrace: never
    include-message: never
  servlet:
    session:
      cookie:
        secure: true
        http-only: true
        same-site: lax

spring:
  config:
    import: optional:vault://        # secrets from HashiCorp Vault (spring-cloud-vault)
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 10MB

management:
  server:
    port: 9090                       # internal-only port
  endpoints:
    web:
      exposure:
        include: health, info, prometheus
  endpoint:
    health:
      show-details: when-authorized
Output
(Actuator now answers only on port 9090, exposes three endpoints, and error responses no longer leak stack traces.)

Security headers: HSTS and Content Security Policy

Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .headers(headers -> headers
            .httpStrictTransportSecurity(hsts -> hsts
                .includeSubDomains(true)
                .maxAgeInSeconds(31536000))
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; img-src 'self' data:; script-src 'self'; frame-ancestors 'none'"))
            .referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)))
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .formLogin(Customizer.withDefaults());
    return http.build();
}
Output
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Content-Security-Policy: default-src 'self'; img-src 'self' data:; script-src 'self'; frame-ancestors 'none'
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

Auditing authentication failures

Java
@Component
public class AuthenticationAuditListener {

    private static final Logger log = LoggerFactory.getLogger(AuthenticationAuditListener.class);

    @EventListener
    public void onSuccess(AuthenticationSuccessEvent event) {
        log.info("login_success user={}", event.getAuthentication().getName());
    }

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent event) {
        // never log the submitted password
        log.warn("login_failure user={} reason={}",
            event.getAuthentication().getName(),
            event.getException().getClass().getSimpleName());
    }
}

// Spring Boot publishes these events automatically when this bean exists
@Bean
AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
    return new DefaultAuthenticationEventPublisher(publisher);
}
Output
INFO  login_success user=asha@webnest.in
WARN  login_failure user=asha@webnest.in reason=BadCredentialsException
WARN  login_failure user=admin reason=BadCredentialsException

Dependency vulnerability scanning in the Maven build

Java
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>12.1.0</version>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>
    </configuration>
</plugin>

# run in CI
mvn org.owasp:dependency-check-maven:check
Output
[INFO] Analysis Complete
[ERROR] One or more dependencies were identified with vulnerabilities that have a CVSS score greater than or equal to '7.0':
[ERROR] example-lib-1.2.3.jar: CVE-2026-XXXXX (8.1)
[INFO] BUILD FAILURE

Common Mistakes

  • Setting management.endpoints.web.exposure.include=* in production, exposing env and heapdump to the internet.
  • Printing secrets at startup or logging full request bodies that contain passwords or tokens.
  • Forgetting forward-headers-strategy behind a proxy, so redirects and OAuth2 callback URLs use http:// instead of https://.
  • Leaving login and password-reset endpoints without any rate limit.
  • Staying on an end-of-life Spring Boot version that no longer receives security patches.

Key Points to Remember

  • Enforce HTTPS, HSTS and a Content Security Policy; keep Spring Security's default headers.
  • Load secrets from the environment or a secret manager, never from committed files.
  • Expose only necessary Actuator endpoints, ideally on an internal management port.
  • Validate input, use parameterised queries, limit uploads and guard against SSRF.
  • Rate-limit authentication endpoints, audit login events and scan dependencies continuously.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.