Vai al contenuto principale
SecurityJavaSpring Boot

Secure Coding in Spring Boot: Le 10 Regole che Seguo Ogni Giorno

FC

Francesco Careri

Senior Software Engineer

28 dicembre 20254 min lettura
Condividi:

Dopo anni di sviluppo enterprise, ho distillato le best practice di sicurezza che applico in ogni progetto: da input validation alla gestione dei secret, ecco la mia checklist.

Dopo 6 anni di sviluppo enterprise, ho visto di tutto: SQL injection in produzione, password in chiaro nei log, endpoint senza autenticazione. Per evitare tutto ciò, ecco le regole che seguo religiosamente in ogni progetto Spring Boot.

1Input Validation: Mai fidarsi dell'utente

La prima regola è semplice: ogni input è potenzialmente malevolo. Non importa se arriva da un form, un'API, o un file...Valida sempre!

In Spring Boot uso una combinazione di Bean Validation e validatori custom:

JAVA
@RestController
public class UserController {

    @PostMapping("/users")
    public ResponseEntity<User> createUser(
            @Valid @RequestBody UserDTO dto) {
        
        // La validazione è già avvenuta grazie a @Valid
        return userService.create(dto);
    }
}

public record UserDTO(
    @NotBlank
    @Size(min = 2, max = 50)
    String name,
    
    @Email
    @NotBlank
    String email,
    
    @Pattern(regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).{8,}$")
    String password
) {}
💡
Pro tipUsa `@Validated` a livello di classe per validare anche i path variables e query parameters.

2SQL Injection: Prepared Statement sempre

Nel 2025, trovare SQL injection in produzione è imbarazzante, eppure succede ancora. La regola è semplice: mai concatenare stringhe per costruire query.

🚫
Da evitareMai fare questo: `String query = "SELECT * FROM users WHERE id = " + userId;`

Usa sempre JPA o Prepared Statement:

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

// ✅ Corretto: Named Parameters
jdbcTemplate.query(
    "SELECT * FROM users WHERE status = ?",
    new Object[]{status},
    userRowMapper
);

3Gestione dei Secret: Mai nel codice

I secret non devono mai apparire nel codice sorgente, uso sempre variabili d'ambiente o secret manager.

YAML
# application.yml - MAI così
spring:
  datasource:
    password: miaPasswordSegreta123  # ❌ NO!

# application.yml - Sempre così
spring:
  datasource:
    password: ${DB_PASSWORD}  # ✅ Variabile d'ambiente
💡
Pro tipPer ambienti production, usa un secret manager come HashiCorp Vault o AWS Secrets Manager.

4CORS: Configurazione esplicita

Mai lasciare CORS aperto a tutti! Configura esplicitamente le origini permesse:

JAVA
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins(
                "https://miosito.com",
                "https://app.miosito.com"
            )
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("Authorization", "Content-Type")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

5Rate Limiting: Protezione da abuse

Ogni API pubblica dovrebbe avere rate limiting. Uso Bucket4j o Redis:

JAVA
@Component
public class RateLimitFilter extends OncePerRequestFilter {
    
    private final Bucket bucket = Bucket.builder()
        .addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
        .build();
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response,
                                    FilterChain chain) {
        if (bucket.tryConsume(1)) {
            chain.doFilter(request, response);
        } else {
            response.setStatus(429);
            response.getWriter().write("Too many requests");
        }
    }
}

6Logging Sicuro: Mai loggare dati sensibili

I log sono una miniera d'oro per gli attaccanti. Mai loggare:

  • Password o token
  • Numeri di carta di credito
  • Dati personali (email, telefono, CF)
  • JAVA
    // ❌ MAI
    log.info("User login: email={}, password={}", email, password);
    
    // ✅ SEMPRE
    log.info("User login attempt: email={}", maskEmail(email));

    7HTTPS Everywhere

    In produzione, sempre e solo HTTPS:

    JAVA
    @Configuration
    public class SecurityConfig {
        
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http
                .requiresChannel(channel -> 
                    channel.anyRequest().requiresSecure()
                )
                // ... altre configurazioni
            ;
            return http.build();
        }
    }

    8Security Headers

    Aggiungo sempre gli header di sicurezza:

    JAVA
    @Configuration
    public class SecurityHeadersConfig implements WebMvcConfigurer {
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new HandlerInterceptor() {
                @Override
                public boolean preHandle(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler) {
                    response.setHeader("X-Content-Type-Options", "nosniff");
                    response.setHeader("X-Frame-Options", "DENY");
                    response.setHeader("X-XSS-Protection", "1; mode=block");
                    response.setHeader("Strict-Transport-Security", 
                        "max-age=31536000; includeSubDomains");
                    return true;
                }
            });
        }
    }

    9Dependency Check: Vulnerabilità note

    Uso OWASP Dependency Check nel build:

    XML
    <style="color: #f07178;">plugin>
        <style="color: #f07178;">groupId>org.owasp</style="color: #f07178;">groupId>
        <style="color: #f07178;">artifactId>dependency-check-maven</style="color: #f07178;">artifactId>
        <style="color: #f07178;">version>8.4.0</style="color: #f07178;">version>
        <style="color: #f07178;">executions>
            <style="color: #f07178;">execution>
                <style="color: #f07178;">goals>
                    <style="color: #f07178;">goal>check</style="color: #f07178;">goal>
                </style="color: #f07178;">goals>
            </style="color: #f07178;">execution>
        </style="color: #f07178;">executions>
    </style="color: #f07178;">plugin>

    10Error Handling: Mai esporre dettagli

    In produzione, gli errori non devono mai esporre stack trace o dettagli interni:

    JAVA
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        
        @ExceptionHandler(Exception.class)
        public ResponseEntity<ErrorResponse> handleException(Exception ex) {
            // Log interno con dettagli
            log.error("Errore interno", ex);
            
            // Risposta generica all'utente
            return ResponseEntity
                .status(500)
                .body(new ErrorResponse(
                    "Si è verificato un errore. Riprova più tardi.",
                    Instant.now()
                ));
        }
    }

    Conclusione

    La sicurezza non è un feature da aggiungere alla fine, ma un mindset da avere dall'inizio. Queste 10 regole sono il minimo indispensabile per ogni progetto Spring Boot in produzione.

    Vuoi approfondire? Scrivimi, sono sempre felice di discutere di sicurezza applicativa.

    Ti è piaciuto l'articolo? Condividilo!

    Condividi:

    FC

    Francesco Careri

    Senior Software Engineer @ ReActive (Gruppo AlmavivA). Scrivo di sviluppo enterprise, security e le cose che imparo lungo il percorso.