Saltar al contenido principal
Version: Next (5.0)

Auditoría de seguridad

AWE registra los eventos de seguridad de una aplicación: inicios y cierres de sesión, bloqueos de cuentas, cambios de contraseña y de segundo factor, aprovisionamiento SSO, accesos denegados y tokens REST. Cada evento lleva el usuario, el tenant, la dirección del cliente, el agente de usuario, un hash de la sesión y un identificador de correlación, y se envía a uno o varios destinos (sinks): una tabla de base de datos, una línea de log JSON para un SIEM y un contador de Micrometer.

La auditoría está activada por defecto. El único cambio visible para una aplicación existente es la tabla AweSecAudEvt, que crea la migración de Flyway AWE_V1.3.0, y la pantalla Auditoría de seguridad del menú de seguridad.

Si quiereConsulte
Desactivar toda la funcionalidadActivarla y desactivarla
Saber qué se registraModelo de evento, Catálogo de eventos
Enviar los eventos a un SIEM o no guardarlos en la base de datosDestinos
Bloquear cuentas tras inicios de sesión fallidosBloqueo automático
Cumplir la normativa de protección de datosPrivacidad
Consultar los eventosPantalla de auditoría de seguridad

Activarla y desactivarla​

awe.security.audit.enabled es el interruptor general. Con false ningún destino registra eventos y no se purga nada; el servicio de auditoría sigue disponible sin hacer nada, de modo que el código que publica eventos no necesita comprobaciones.

# Desactivar la auditoría de seguridad
awe.security.audit.enabled=false

Cada destino tiene además su propio interruptor (consulte Propiedades). Los productos que ya auditan por su cuenta pueden, por ejemplo, mantener el log JSON y prescindir de la tabla:

awe.security.audit.database.enabled=false

Modelo de evento​

Un evento es un com.almis.awe.model.security.audit.SecurityAuditEvent. El framework completa los datos de la petición al publicar el evento; los valores ya informados en el evento se conservan.

CampoContenido
idIdentificador único (UUID)
timestampMomento del evento
typeTipo de evento, consulte el catálogo
outcomeSUCCESS o FAILURE
causeMotivo, sobre todo de los fallos (puede estar vacío)
usernameUsuario implicado, tal como se escribió en el inicio de sesión o el usuario autenticado (hasta 255 caracteres)
tenantTenant SSO de la petición o, si no lo hay, el alias de base de datos de la sesión (hasta 100 caracteres)
channelWEB (aplicación web), SSO (proveedor de identidad), REST (peticiones bajo /api/) o SYSTEM (generado por el servidor fuera de cualquier petición, como la caducidad de una sesión)
ipAddressDirección del cliente, anonimizada si así se configura
userAgentCabecera User-Agent del cliente (hasta 512 caracteres)
sessionHashSHA-256 del identificador de sesión. El identificador de sesión nunca se guarda
correlationIdIdentificador común a los eventos de una petición: la cabecera X-Correlation-Id, si no X-Request-Id, si no el traceId del MDC (trazas) y, en último caso, uno generado por petición. Se ignoran los valores de cabecera que no tengan de 1 a 64 caracteres entre letras, dígitos, ., _, : o -
detailDetalle libre: opción, URL, perfil, administrador, código de error del proveedor... (hasta 1000 caracteres)

Catálogo de eventos​

Los valores de tipo y causa son los enumerados SecurityAuditEventType y SecurityAuditCause. El "canal" es el de la petición salvo que se indique un valor.

TipoSe genera cuandoResultado y causaCanal
LOGINTermina un inicio de sesión local (formulario, /action/login, POST /api/authenticate)SUCCESS; FAILURE con BAD_CREDENTIALS (contraseña incorrecta o usuario desconocido), LOCKED, DISABLED, ACCOUNT_EXPIRED, CREDENTIALS_EXPIRED, LOCAL_LOGIN_DISABLED o ERRORWEB, REST
LOGINTermina un inicio de sesión SSOSUCCESS; FAILURE con LOCKED, DISABLED... (cuenta rechazada por AWE), ROLE_NOT_MAPPED, PROVIDER_ERROR (detalle: código de error OAuth2) o ERRORSSO
LOGOUTEl usuario cierra la sesiónSUCCESS, EXPLICITCanal de la petición, SSO en sesiones SSO
LOGOUTEl proveedor de identidad termina la sesión (OIDC back-channel logout)SUCCESS, PROVIDERSSO
LOGOUTCaduca una sesión iniciadaSUCCESS, EXPIREDSYSTEM
ACCOUNT_LOCKEDEl bloqueo automático bloquea la cuentaSUCCESS, MAX_ATTEMPTSWEB, REST
ACCOUNT_LOCKEDUn administrador bloquea al usuario en la pantalla de usuariosSUCCESS, ADMINISTRATOR (detalle: administrator: <usuario>)WEB
ACCOUNT_UNLOCKEDUn administrador desbloquea al usuario, o restablece la contraseña de un usuario bloqueado, en la pantalla de usuariosSUCCESS, ADMINISTRATORWEB
PASSWORD_CHANGEEl usuario cambia la contraseña en las pantallas de cambio de contraseñaSUCCESS; FAILURE, BAD_CREDENTIALS (contraseña actual incorrecta)WEB
PASSWORD_CHANGEEl usuario sustituye una contraseña caducada desde el inicio de sesiónSUCCESS, CREDENTIALS_EXPIREDWEB
PASSWORD_RESETUn administrador establece una contraseña nueva en la pantalla de usuariosSUCCESS, ADMINISTRATORWEB
TOTP_ENROLLMENTSe genera y guarda un secreto de segundo factorSUCCESS; FAILURE con FORBIDDEN (una sesión pendiente de su código TOTP intenta rotar el secreto) o ERROR (no se pudo guardar el secreto)WEB
TOTP_VERIFICATIONEl usuario introduce el código TOTP tras iniciar sesiónSUCCESS; FAILURE, INVALID_CODEWEB
TOTP_RESETEl usuario desactiva el segundo factor, o un administrador lo restableceSUCCESS (sin causa, o ADMINISTRATOR)WEB
SSO_PROVISIONINGSe crea un usuario a partir de los datos del proveedor de identidad (awe.security.sso.auto-provision-user)SUCCESS (detalle: perfil)SSO
SSO_PROFILE_SYNCEl perfil de un usuario se actualiza a partir de los roles del proveedorSUCCESS (detalle: <anterior> -> <nuevo>)SSO
ACCESS_DENIEDUna opción que existe en el menú está restringida para el perfil del usuarioFAILURE, RESTRICTED (detalle: option: <nombre>)WEB
ACCESS_DENIEDSpring Security deniega una URL a un usuario autenticadoFAILURE, FORBIDDEN (detalle: url: <ruta>)Canal de la petición
TOKEN_ISSUEDPOST /api/authenticate emite un JWTSUCCESSREST
TOKEN_REJECTEDUna petición REST lleva un JWT mal formado, mal firmado o caducadoFAILURE, INVALID_TOKEN o TOKEN_EXPIREDREST

Destinos​

El servicio de auditoría envía cada evento a todos los beans SecurityAuditSink. El registro se hace en la medida de lo posible: un destino que falla se registra en el log y no detiene a los demás destinos ni a la operación auditada. Los eventos publicados dentro de una transacción (por ejemplo, un bloqueo hecho por un maintain) se envían después del commit y se descartan si la transacción hace rollback, de modo que solo describen cambios que se han producido de verdad.

Base de datos​

DatabaseSecurityAuditSink guarda los eventos en la tabla de solo inserción AweSecAudEvt, que crea la migración AWE_V1.3.0__Security_audit_events.sql en todas las bases de datos soportadas. El script se puede volver a aplicar: la tabla y sus índices solo se crean si no existen. Cada evento se escribe en su propia conexión del DataSource de la aplicación y se confirma al momento, fuera de la transacción de la operación auditada. Las filas solo se borran con la purga.

ColumnaCampoColumnaCampo
IdeSecAudidChnchannel
EvtDattimestampIpAdripAddress
EvtTyptypeUsrAgtuserAgent
EvtResoutcomeSesHshsessionHash
EvtCaucauseCorIdecorrelationId
UsrNamusernameDetdetail
TentenantEvtDaydía de timestamp

EvtDay es el día del evento en la zona horaria del servidor: los filtros de fecha y la gráfica diaria de la pantalla comparan fechas sin hora sobre esa columna, así que se comportan igual en todas las bases de datos y usan su índice.

Índices: EvtDat, (UsrNam, EvtDat), (EvtTyp, EvtDat) y EvtDay.

Retención. La purga borra los eventos con más de awe.security.audit.database.retention-days días (365 por defecto; 0 los conserva para siempre) según la planificación awe.security.audit.database.purge-cron (por defecto, todos los días a las 03:00, en la zona horaria del servidor). Se ejecuta en su propio hilo, así que la aplicación no necesita @EnableScheduling. Con varios nodos, cada nodo ejecuta el mismo borrado, que es idempotente.

Log JSON​

LogSecurityAuditSink escribe cada evento como una línea JSON, con nivel INFO, en el logger awe.security.audit. Los campos vacíos se omiten:

{"timestamp":"2026-10-16T08:15:30.123Z","id":"0b5f7c1e-6c1d-4f7e-9a4b-3f2d1c0e9a87","type":"LOGIN","outcome":"FAILURE","cause":"BAD_CREDENTIALS","username":"jdoe","tenant":"awemysql","channel":"WEB","ipAddress":"10.0.12.0","userAgent":"Mozilla/5.0 (X11; Linux x86_64)","sessionHash":"9f2c...e41a","correlationId":"4d1f6a0c-2b7e-4a51-8c0f-7d5e2a9b1c33"}

Para enviar los eventos a un SIEM, dirija ese logger a su propio fichero en su logback-spring.xml:

logback-spring.xml
<appender name="SECURITY_AUDIT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH:-logs}/security-audit.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH:-logs}/security-audit.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>

<logger name="awe.security.audit" level="INFO" additivity="false">
<appender-ref ref="SECURITY_AUDIT"/>
</logger>

Si el nivel del logger es superior a INFO, no se escribe nada.

Contador de Micrometer​

Con el starter de observabilidad, MicrometerSecurityAuditSink cuenta los eventos en awe.security.audit.events, con las etiquetas type, outcome, channel y cause (none si están vacías). El usuario, la dirección y los demás valores libres nunca son etiquetas.

Destinos propios​

Declare un bean que implemente com.almis.awe.security.audit.SecurityAuditSink. Recibe cada evento ya completado con los datos de la petición:

@Bean
public SecurityAuditSink kafkaSecurityAuditSink(KafkaTemplate<String, String> kafka) {
return event -> kafka.send("security-audit", event.getType().name(), event.getId());
}

Los destinos incluidos son @ConditionalOnMissingBean: un bean de la misma clase los sustituye. Para publicar eventos desde su propio código (por ejemplo, un flujo de autenticación propio), inyecte SecurityAuditService y llame a success, failure o publish.

Bloqueo automático​

El bloqueo automático está desactivado por defecto. Asigne a awe.security.lockout.max-attempts el número de contraseñas incorrectas consecutivas tras el que se bloquea una cuenta local:

awe.security.lockout.max-attempts=5
PasoQué ocurre
Contraseña incorrecta de un usuario existenteEl contador ope.NumLog aumenta en uno. Los demás fallos (bloqueado, deshabilitado, caducado...) y los usuarios desconocidos no cuentan
El contador llega a max-attemptsLa cuenta se bloquea (ope.PwdLck = 1) y se registra un evento ACCOUNT_LOCKED con causa MAX_ATTEMPTS. Los siguientes inicios de sesión fallan con causa LOCKED
Inicio de sesión correctoEl contador vuelve a 0
Un administrador desbloquea al usuario (o restablece la contraseña) en la pantalla de usuariosLa cuenta se desbloquea, el contador se pone a cero y se registra un evento ACCOUNT_UNLOCKED

El bloqueo solo se aplica a los inicios de sesión locales (formulario, /action/login y /api/authenticate); con SSO, el bloqueo corresponde al proveedor de identidad. Funciona aunque la auditoría esté desactivada. Un fallo al contar nunca cambia el resultado del inicio de sesión.

Privacidad​

  • Anonimización de la IP. awe.security.audit.anonymize-ip=true guarda la dirección del cliente con el último octeto de las direcciones IPv4 a cero (10.0.12.34 → 10.0.12.0) y las direcciones IPv6 cortadas tras los primeros 48 bits (2001:db8:85a3:8d3:1319:8a2e:370:7348 → 2001:db8:85a3::). Las direcciones que no se pueden interpretar se descartan, nunca se guardan en claro.
  • Sesiones. Solo se guarda el hash SHA-256 del identificador de sesión, de modo que los eventos se pueden agrupar por sesión pero el identificador no se puede reutilizar.
  • Detrás de un proxy o balanceador la dirección es la del proxy salvo que se indique a Spring Boot que confíe en las cabeceras reenviadas: establezca server.forward-headers-strategy=native o framework (consulte Logging, base de datos y web para los despliegues war).
  • Retención. Ajuste awe.security.audit.database.retention-days al periodo que permita su política de protección de datos.

Pantalla de auditoría de seguridad​

Configuración > Seguridad > Auditoría de seguridad (opción security-audit, pantalla SecAud) consulta los eventos que guarda el destino de base de datos. Es una pantalla XML genérica que muestran tanto el cliente Angular como el cliente React.

ParteContenido
CriteriosRango de fechas (por defecto, los últimos 7 días), tipos de evento, resultado, canal, usuario e IP (coincidencia parcial)
Actividad por díaGráfico de columnas apiladas con los eventos correctos y fallidos
Fallos por causaGráfico circular de los fallos
EventosTabla paginada, de más reciente a más antiguo: fecha, evento, resultado, usuario, causa, canal, IP y detalle
VerDetalle del evento seleccionado (pantalla SecAudViw, opción view-security-audit-event), con tenant, identificador, identificador de correlación, hash de sesión y agente de usuario

Las aplicaciones que definen su propio menu/private.xml (los arquetipos lo hacen) deben añadir la opción a su grupo de seguridad para mostrar la pantalla:

<option name="security-audit" label="MENU_TOOLS_SECURITY_AUDIT" screen="SecAud" icon="shield">
<option name="view-security-audit-event" screen="SecAudViw" invisible="true"/>
</option>

La pantalla es una opción de menú normal: restrínjala a los perfiles que puedan verla con las restricciones de perfil y de usuario habituales (consulte Mecanismos de seguridad). Sin el destino de base de datos la pantalla no muestra eventos.

Propiedades​

PropiedadValor por defectoDescripción
awe.security.audit.enabledtrueRegistra los eventos de seguridad. Con false ningún destino registra nada y no se purga nada
awe.security.audit.anonymize-ipfalseAnonimiza la dirección del cliente de cada evento (último octeto en IPv4, a partir de los primeros 48 bits en IPv6)
awe.security.audit.database.enabledtrueGuarda los eventos en la tabla AweSecAudEvt
awe.security.audit.database.retention-days365Días que se conserva un evento antes de que la purga lo borre. 0 conserva los eventos para siempre
awe.security.audit.database.purge-cron0 0 3 * * *Expresión cron de Spring (zona horaria del servidor) de la purga
awe.security.audit.log.enabledtrueEscribe cada evento como una línea JSON en el logger awe.security.audit
awe.security.audit.metrics.enabledtrueCuenta los eventos en el contador awe.security.audit.events (starter de observabilidad)
awe.security.lockout.max-attempts0Contraseñas incorrectas consecutivas tras las que se bloquea una cuenta local. 0 desactiva el bloqueo

También aparecen en la referencia de propiedades.

Limitaciones​

  • Sin control de sesiones concurrentes. AWE no limita las sesiones de un usuario; la auditoría registra los cierres de sesión explícitos, del proveedor y por caducidad.
  • Los flujos de autenticación propios (filtros, controladores u otros puntos de entrada propios que autentican usuarios sin pasar por el inicio de sesión de AWE) no se registran salvo que llamen a SecurityAuditService.
  • La purga se ejecuta en cada nodo. El borrado es idempotente, así que varios nodos solo repiten el mismo trabajo.
  • Varias bases de datos. Con awe.database.multidatabase-enable=true el DataSource de la aplicación enruta según la base de datos de la sesión: los eventos generados en una petición se guardan en esa base de datos, y la purga, que se ejecuta fuera de cualquier petición, limpia la base de datos por defecto.