# Regla: Seguridad Java

Esta regla es OBLIGATORIA. Ningún endpoint, servicio o componente se considera
completo si viola alguno de los puntos aquí listados.
Las vulnerabilidades de seguridad en Java tienen consecuencias predecibles
y conocidas — no hay excusa para repetirlas.

---

## PreparedStatement SIEMPRE — nunca concatenar SQL

La inyección SQL sigue siendo la vulnerabilidad #1 en aplicaciones Java.
Es 100% evitable con parameterized queries.

```java
// MAL — SQL injection garantizada
String query = "SELECT * FROM usuarios WHERE email = '" + email + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);

// MAL — también vulnerable aunque parezca diferente
String query = String.format("SELECT * FROM facturas WHERE id = %s", id);

// BIEN — PreparedStatement con parámetros nombrados
String query = "SELECT * FROM usuarios WHERE email = ? AND activo = ?";
try (var stmt = conn.prepareStatement(query)) {
    stmt.setString(1, email);
    stmt.setBoolean(2, true);
    ResultSet rs = stmt.executeQuery();
}

// BIEN — con JPA/Spring Data (ORM hace el trabajo)
List<Usuario> usuarios = repositorio.findByEmailAndActivo(email, true);
```

Si se requiere SQL dinámico, usar `JdbcTemplate` con `?` o named parameters,
nunca `String.format()` ni concatenación.

---

## Validación con @Valid y Bean Validation

Toda entrada del usuario DEBE validarse en el servidor. La validación en frontend
es UX, no seguridad.

```java
// DTO con restricciones de Bean Validation
public record CrearFacturaRequest(
    @NotNull(message = "El cliente es obligatorio")
    UUID clienteId,

    @NotEmpty(message = "La factura debe tener al menos una línea")
    @Size(max = 100, message = "Máximo 100 líneas por factura")
    List<@Valid LineaRequest> lineas,

    @DecimalMin(value = "0.0", message = "El descuento no puede ser negativo")
    @DecimalMax(value = "100.0", message = "El descuento no puede superar 100%")
    BigDecimal descuentoPorcentaje
) {}

// Controller — @Valid activa la validación automáticamente
@PostMapping("/facturas")
public ResponseEntity<FacturaDto> crear(@RequestBody @Valid CrearFacturaRequest request) {
    // Si llega aquí, request ya fue validado
}
```

---

## Spring Security: CORS, CSRF y sesiones

```java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            // CORS desde variables de entorno, nunca hardcodeado
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            // CSRF: habilitar para apps con sesiones; deshabilitar solo en APIs stateless con JWT
            .csrf(csrf -> csrf.disable())  // APIs REST con JWT — documentar por qué
            // Sesiones stateless para APIs REST
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // Headers de seguridad
            .headers(headers -> headers
                .frameOptions(frame -> frame.deny())
                .xssProtection(xss -> xss.disable())  // CSP es mejor que X-XSS-Protection
                .contentSecurityPolicy(csp ->
                    csp.policyDirectives("default-src 'self'")))
            .build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        var config = new CorsConfiguration();
        // Leer desde environment, NUNCA hardcodear
        config.setAllowedOrigins(List.of(env.getProperty("cors.allowed-origins").split(",")));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
        var source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}
```

---

## Secretos vía variables de entorno

```java
// MAL — secreto hardcodeado en el código fuente
private static final String JWT_SECRET = "mi-secreto-super-secreto";
private static final String DB_PASSWORD = "admin123";

// MAL — secreto en application.properties que se versiona
// application.properties:
// spring.datasource.password=admin123

// BIEN — variables de entorno inyectadas
@Value("${JWT_SECRET}")
private String jwtSecret;

// BIEN — application.properties usa placeholders
// application.properties:
// spring.datasource.password=${DB_PASSWORD}
// jwt.secret=${JWT_SECRET}
```

El archivo `.env` es solo para desarrollo local y SIEMPRE está en `.gitignore`.
En producción, las variables se inyectan desde Vault, AWS Secrets Manager o el
sistema de deployment.

---

## No deserializar objetos no confiables

```java
// MAL — deserialización insegura: vector de RCE (Remote Code Execution)
ObjectInputStream ois = new ObjectInputStream(inputStreamDeUsuario);
Object objeto = ois.readObject();  // NUNCA con datos no confiables

// BIEN — usar JSON con Jackson o protobuf, no serialización Java nativa
ObjectMapper mapper = new ObjectMapper();
FacturaDto dto = mapper.readValue(jsonInput, FacturaDto.class);

// BIEN — si se requiere deserialización Java, usar filtros de clase
ObjectInputStream ois = new ObjectInputStream(input) {
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
        if (!desc.getName().startsWith("com.miempresa.")) {
            throw new InvalidClassException("Clase no permitida: " + desc.getName());
        }
        return super.resolveClass(desc);
    }
};
```

---

## OWASP Dependency Check en CI

```xml
<!-- pom.xml — falla el build si hay CVE de severidad >= 8.0 -->
<plugin>
  <groupId>org.owasp</groupId>
  <artifactId>dependency-check-maven</artifactId>
  <version>9.0.10</version>
  <configuration>
    <failBuildOnCVSS>8</failBuildOnCVSS>
    <suppressionFile>owasp-suppressions.xml</suppressionFile>
  </configuration>
  <executions>
    <execution>
      <goals><goal>check</goal></goals>
    </execution>
  </executions>
</plugin>
```

---

## Logging sin datos sensibles (SLF4J + Logback)

```java
// MAL — datos sensibles en logs
log.info("Usuario autenticado: {} con contraseña: {}", email, password);
log.debug("Token JWT generado: {}", jwtToken);
log.error("Error de BD al procesar: {}", tarjetaCredito.getNumero());

// BIEN — log solo información no sensible
log.info("Autenticación exitosa para usuario: {}", email);
log.debug("Token JWT generado para sesión: {}", sesionId);
log.error("Error de BD al procesar pedido: {}", pedidoId);
```

Configurar en `logback.xml` un `PatternLayout` que sanitice campos sensibles
si el logging de requests completos es necesario para debugging.

---

## Headers de seguridad en Spring Boot

```yaml
# application.yml — headers mínimos de seguridad
server:
  servlet:
    session:
      cookie:
        http-only: true
        secure: true
        same-site: strict
```

Verificar con OWASP ZAP o similar que los headers `Strict-Transport-Security`,
`X-Content-Type-Options`, `X-Frame-Options` y `Content-Security-Policy` están
presentes en las respuestas de producción.

---

## Checklist de seguridad Java antes de abrir PR

- [ ] Sin concatenación de strings en queries SQL — solo PreparedStatement o JPA
- [ ] Inputs validados con @Valid y Bean Validation en todos los endpoints
- [ ] Sin secretos hardcodeados — variables de entorno en todos los ambientes
- [ ] Sin ObjectInputStream con datos de usuarios externos
- [ ] CORS configurado desde variables de entorno, no hardcodeado
- [ ] OWASP Dependency Check en CI sin CVEs críticos sin suprimir
- [ ] Logs sin passwords, tokens ni datos personales
- [ ] Headers de seguridad configurados en Spring Security
