La auditoría es uno de los requisitos clásicos en sectores regulados: banca, sanidad, sector público y cualquier sistema con datos personales bajo GDPR. La obligación es saber quién hizo qué, cuándo y desde dónde.
Spring Security publica eventos de seguridad como ApplicationEvent del contenedor Spring. Suscribirse a ellos y enriquecerlos con un correlation ID propagado entre microservicios da una solución limpia, desacoplada del código de negocio.
Eventos que publica Spring Security
| Evento | Cuándo se publica |
|---|---|
| AuthenticationSuccessEvent | Login correcto |
| AbstractAuthenticationFailureEvent | Login fallido (credenciales malas, cuenta bloqueada, etc.) |
| LogoutSuccessEvent | Logout completado |
| SessionFixationProtectionEvent | Tras migrateSession |
| AuthenticationSwitchUserEvent | Tras switchUser (impersonación) |
| AuthorizationGrantedEvent | Acceso permitido a un recurso protegido |
| AuthorizationDeniedEvent | Acceso denegado |
| InteractiveAuthenticationSuccessEvent | Subset de SuccessEvent: solo logins interactivos |
Por defecto, los eventos de autorización no se publican por su coste. Se activan con
SecurityObservationSettings.shouldObserveAuthorizations(true).
Listener centralizado
Un único listener captura todos los eventos relevantes y los normaliza a un formato común:
@Component
public class SecurityAuditListener {
private final AuditEventPublisher publisher;
@EventListener
public void handle(AbstractAuthenticationEvent event) {
AuditRecord record = AuditRecord.builder()
.timestamp(Instant.now())
.traceId(MDC.get("traceId"))
.userId(event.getAuthentication().getName())
.eventType(eventType(event))
.ip(extractIp(event))
.userAgent(extractUserAgent(event))
.build();
publisher.publish(record);
}
@EventListener
public void handleAuthorization(AuthorizationDeniedEvent<?> event) {
AuditRecord record = AuditRecord.builder()
.timestamp(Instant.now())
.traceId(MDC.get("traceId"))
.userId(event.getAuthentication().get().getName())
.eventType("AUTHORIZATION_DENIED")
.resource(event.getObject().toString())
.build();
publisher.publish(record);
}
private String eventType(AbstractAuthenticationEvent event) {
if (event instanceof AuthenticationSuccessEvent) return "AUTH_SUCCESS";
if (event instanceof AbstractAuthenticationFailureEvent) return "AUTH_FAILURE";
if (event instanceof LogoutSuccessEvent) return "LOGOUT";
return "AUTH_OTHER";
}
}
Estructura del AuditRecord
Diseña un esquema estable para todos los eventos. Cambiarlo después complica el análisis histórico.
public record AuditRecord(
Instant timestamp,
String eventType,
String traceId,
String spanId,
String userId,
String tenantId,
String ip,
String userAgent,
String resource,
String result,
Map<String, Object> details
) {}
eventType: enum cerrado y documentado.traceId/spanId: del W3C Trace Context, útil para cruzar con trazas APM.userId: subject del JWT o nombre del usuario; nunca el email para no filtrar PII en logs.tenantId: imprescindible en multi-tenant.details: campo libre para datos específicos del evento, sin obligar a cambiar el esquema.
Correlation ID entre microservicios
El W3C Trace Context estandariza dos cabeceras que cualquier tracer moderno respeta.
traceparent: identifica la traza completa con untraceIdy unspanId.tracestate: información de vendor.
Spring Boot 3 con Micrometer Tracing añade y propaga estas cabeceras automáticamente. Solo hay que asegurar que el listener las lee del MDC.
management:
tracing:
sampling:
probability: 1.0
observations:
annotations:
enabled: true
Y para asegurar la propagación entre llamadas, usa RestClient o WebClient con la integración de Micrometer:
@Bean
RestClient restClient(RestClient.Builder builder) {
return builder.build(); // Spring Boot ya añade el interceptor de tracing
}
Sin propagación, el
traceIdcambia en cada salto y la auditoría queda fragmentada. Con propagación, todas las acciones de una petición de usuario comparten el mismotraceId.
Persistir auditoría en Kafka
Para una auditoría real, los eventos no se quedan en logs locales: se publican a un broker (Kafka, RabbitMQ, AWS Kinesis) que los entrega a un sistema de archivado (S3, BigQuery, ClickHouse).
@Component
public class KafkaAuditPublisher implements AuditEventPublisher {
private final KafkaTemplate<String, AuditRecord> kafka;
@Override
public void publish(AuditRecord record) {
kafka.send("security-audit", record.userId(), record);
}
}
Kafka garantiza:
- Durabilidad (los eventos sobreviven a reinicios).
- Orden por partición (eventos de un mismo usuario llegan ordenados).
- Reprocessing (puedes recargar todo el histórico en un nuevo consumer si cambias el esquema).
Configuración mínima:
spring:
kafka:
bootstrap-servers: kafka:9092
producer:
acks: all
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
properties:
enable.idempotence: true
acks: all y enable.idempotence: true aseguran que ningún evento se pierde ni se duplica.
Anti-patrón: auditar dentro de la transacción de negocio
@Transactional
public void transferir(Long origen, Long destino, BigDecimal cantidad) {
cuentas.debitar(origen, cantidad);
cuentas.acreditar(destino, cantidad);
auditoria.registrar("TRANSFER", ...); // mal
}
Si la transacción de negocio rollbackea, también se borra el registro de auditoría. Lo correcto es publicar el evento (síncrono al método) y dejar que el listener lo persista después del commit.
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onTransferencia(TransferenciaEvent event) {
auditoria.registrar("TRANSFER", event.toAudit());
}
Con AFTER_COMMIT, el listener solo se ejecuta si la transacción de negocio fue exitosa. Y si el listener falla, no afecta a la operación principal.
Compliance: GDPR y retención
GDPR exige conservar la auditoría el tiempo mínimo necesario y no más. Define políticas de retención explícitas.
- Eventos de autenticación: 6 meses.
- Acciones de administración: 1 año.
- Operaciones financieras: 5-10 años (según jurisdicción).
En Kafka, configura log.retention.ms por topic. En la BD de archivo, usa particiones por mes y un job que las elimina al pasar el plazo.
Auditoría no es lo mismo que logging. Los logs operativos pueden borrarse con frecuencia; la auditoría tiene marco legal.
Inmutabilidad y firma
Para sectores regulados, los registros de auditoría deben ser inmutables y firmados. Una técnica común:
- Cada evento se hashea con SHA-256.
- El hash se incluye en el siguiente evento (estilo blockchain).
- La cadena se firma periódicamente con una clave del HSM.
Manipular un evento intermedio rompe la cadena de hashes y la firma deja de validar.
Acceso a la auditoría
Define quién puede leer la auditoría y bajo qué condiciones.
- Operador de seguridad: lectura completa con autenticación fuerte.
- Usuario regulado (GDPR): lectura de sus propios eventos via "right to access".
- Auditor externo: vista filtrada por scope, con tokens de duración corta.
Spring Security vuelve a ser la capa que aplica esos permisos:
@PreAuthorize("hasRole('AUDITOR') or #userId == authentication.name")
public List<AuditRecord> consultar(@PathVariable String userId) {
return repo.findByUserId(userId);
}
Errores frecuentes
- Bloquear la petición esperando al persist: usa async o cola para no añadir latencia al usuario.
- Loguear el cuerpo completo de la petición: revela datos personales y secretos. Audita el evento, no el payload.
- Sin schema versioning: si cambias el formato del evento sin versionar, los consumidores rompen al leer históricos.
- Sin alertas sobre eventos críticos: auditar y no alertar es como instalar cámaras y no mirarlas.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Spring Security es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Spring Security
Explora más contenido relacionado con Spring Security y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Implementar auditoría de seguridad con Spring Events nativos, propagar correlation IDs entre microservicios con W3C Trace Context y persistir eventos críticos en una cola de mensajería.