Spring Boot Security Best Practices 2025

Ahmad Sadeddin

CEO at Corgea

Introduction

Spring Boot is widely used for building Java web backends, but it often handles sensitive data and must meet strict compliance requirements. Recent incidents like the Spring4Shell zero-day exploit (2022) showed that even sample Spring code can be dangerously insecure. A breach can damage reputation, incur fines, and can also lead to customers losing trust.

To avoid these risks, software developers must build security from the start. This article outlines key Spring Boot security best practices for 2025. Each section covers a practice with example code, so developers at all levels can quickly apply them.

Enforce HTTPS/TLS

Always use HTTPS (SSL/TLS) to encrypt data in transit. Spring Boot lets you configure an SSL keystore in application.properties, for example:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=yourpassword
server.ssl.key-password=yourpassword

Additionally, force all HTTP requests to redirect to HTTPS in Spring Security. For example, extend WebSecurityConfigurerAdapter:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .requiresChannel()
            .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
            .requiresSecure() // Enforce HTTPS
        .and()
        .authorizeRequests()
            .anyRequest().authenticated()
        .and()
        .csrf().and()
        .formLogin().and()
        .httpBasic();
}

CSRF Protection

Cross-Site Request Forgery (CSRF) attacks can hijack authenticated user actions. Spring Security enables CSRF protection by default. Do not disable it in production. For example, avoid code like:

http.csrf().disable(); // ✗ Don't disable CSRF in production

Input Validation

Never trust user input. Always validate it thoroughly to prevent injection and other attacks. Use Spring's @Valid and Bean Validation annotations in your DTOs. For example:

import javax.validation.constraints.NotEmpty;

public class UserInput {
    @NotEmpty(message = "Name cannot be empty")
    private String name;
    // getters and setters
}

Then in a controller:

@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody @Valid UserInput input) {
    // process validated input
}

Parameterize Your Queries

Avoid building SQL queries via string concatenation. Instead, use Spring Data repositories or JdbcTemplate with parameters. For example, a JPA repository query safely binds the email:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.email = :email")
    User findByEmail(@Param("email") String email);
}

This example uses named parameters (:email), so the query is pre-compiled and safe against injection. Alternatively, with JdbcTemplate you can do:

String sql = "SELECT * FROM users WHERE name = ?";
User user = jdbcTemplate.queryForObject(sql,
    new Object[]{name}, new UserRowMapper());

Always parameterize queries or use Spring Data repositories. Never concatenate raw user input into SQL, which leaves you vulnerable to malicious payloads.

Protect Against XSS

Cross-Site Scripting (XSS) can inject malicious scripts into your pages. To prevent it: 1) set strict security headers and 2) sanitize/encode user content. With Spring Security, configure security headers like Content Security Policy (CSP):

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .contentTypeOptions(new ContentTypeOptionsHeaderWriter())
                .frameOptions(new FrameOptionsHeaderWriter(FrameOptionsHeaderWriter.XFrameOptionsMode.DENY))
                .contentSecurityPolicy("default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';")
            );
        return http.build();
    }
}

Authentication & Authorization

Use Spring Security to configure authentication providers and ensure only authorized users access endpoints. A typical setup uses UserDetailsService with a strong password encoder:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
      .userDetailsService(myUserDetailsService)
      .passwordEncoder(new BCryptPasswordEncoder());
}

This example tells Spring to load users from myUserDetailsService and validate passwords with BCrypt, a strong hashing algorithm. For modern REST APIs, consider token-based auth. Methods like JWT (JSON Web Tokens) are popular for stateless APIs. Always serve tokens only over HTTPS and avoid exposing credentials in URLs.

Method-Level Security

Protect critical service methods with annotations. Spring allows you to put security checks right on methods via @PreAuthorize or @RolesAllowed. For example:

@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
    // deletion logic
}

This ensures only users with the ADMIN role can invoke deleteUser().

Encrypt Sensitive Data

Even with secure transit (HTTPS), data at rest can be at risk. Encrypt any sensitive properties or database fields. For example, using Jasypt's Spring integration, protect secrets in your config:

@EncryptablePropertySource(name="EncryptedProps", value="classpath:encrypted.properties")
@Configuration
public class EncryptionConfig {
    // Jasypt decryption setup
}

Dependency Management and Updates

Keep all libraries and frameworks up-to-date. Many Spring Boot vulnerabilities come from outdated dependencies. Use Maven or Gradle's dependency management to pin secure versions. Tools like OWASP Dependency-Check or Trivy can scan your project for known CVEs. For example, integrate a scan into your CI pipeline:

# Example: Trivy can scan dependencies
trivy fs --security-checks

Logging & Auditing

Maintain detailed logs of security events. Spring Boot's Actuator and Spring Security's auditing support help here. Enable the audit and authentication event listeners:

public class CustomAuditListener extends AbstractAuthenticationAuditListener {
    @Override
    public void onApplicationEvent(AbstractAuthenticationEvent event) {
        // e.g., log login failures or successes
        logger.info("Auth event: " + event.toString());
    }
}

Logging important security events (login failures, role changes, etc.) enables quick detection of attacks.

Ready be secure?

Harden your software in less than 10 mins'