Chuyển đến nội dung chính

Bài 18: Event System và Audit Logging

Event types (Login Events, Admin Events), bật event logging, cấu hình event listeners (jboss-logging, email), Event Store, Event Details, event filtering, truy vấn events qua Admin Console và REST API, custom Event Listener SPI, tích hợp ELK Stack / Grafana Loki cho centralized logging, SIEM integration, audit compliance (SOC2, HIPAA) và alert automation.

🔒 DevSecOps — Bài 18 Bài 18: Event System và Audit Logging

Keycloak từ Cơ bản đến Nâng cao

Phần 5: Themes, Events, Security và Vault

xdev.asia

1. Tổng quan Event System

Keycloak cung cấp hệ thống Event System toàn diện để theo dõi mọi hoạt động trong hệ thống. Mỗi hành động từ đăng nhập, đăng ký, đến thay đổi cấu hình admin đều được ghi lại thành events.

1.1 Hai loại Events

LoạiMô tảVí dụ
Login Events (User Events)Các hành động liên quan đến người dùngLOGIN, REGISTER, LOGOUT, TOKEN_EXCHANGE
Admin EventsCác thay đổi cấu hình qua Admin Console/APICREATE user, UPDATE realm, DELETE client

1.2 Login Event Types

Event TypeMô tảKhi nào xảy ra
LOGINĐăng nhập thành côngUser nhập đúng credentials
LOGIN_ERRORĐăng nhập thất bạiSai username/password
REGISTERĐăng ký tài khoản mớiUser tạo account thành công
REGISTER_ERRORĐăng ký thất bạiEmail trùng, validation fail
LOGOUTĐăng xuấtUser logout hoặc session expired
CODE_TO_TOKENExchange authorization code → tokenOIDC Authorization Code flow
CODE_TO_TOKEN_ERRORToken exchange thất bạiInvalid code, expired code
REFRESH_TOKENRefresh access tokenClient dùng refresh token
REFRESH_TOKEN_ERRORRefresh token thất bạiToken revoked hoặc expired
CLIENT_LOGINClient authenticationService account login
INTROSPECT_TOKENToken introspectionResource server verify token
UPDATE_PASSWORDĐổi mật khẩuUser thay đổi password
RESET_PASSWORDReset mật khẩuUser reset qua email link
VERIFY_EMAILXác thực emailUser click verification link
SEND_RESET_PASSWORDGửi email reset passwordRequest forgot password
UPDATE_PROFILECập nhật hồ sơUser cập nhật thông tin cá nhân
REMOVE_TOTPXóa TOTP deviceUser gỡ OTP authenticator
UPDATE_TOTPCấu hình TOTPUser thiết lập OTP
GRANT_CONSENTUser cấp quyền consentOAuth2 consent screen
TOKEN_EXCHANGEToken exchangeExchange token giữa các clients

1.3 Admin Event Operations

OperationMô tảResource Types
CREATETạo mới resourceUSER, CLIENT, REALM, GROUP, ROLE...
UPDATECập nhật resourceUSER, CLIENT, REALM_SETTINGS...
DELETEXóa resourceUSER, CLIENT, SESSION...
ACTIONThực hiện hành độngRESET_PASSWORD, SEND_VERIFY_EMAIL...

2. Bật Event Logging

2.1 Qua Admin Console

  1. Đăng nhập Admin Console
  2. Chọn Realm → Realm Settings → tab Events
  3. Cấu hình User events settings:
    • Save events: ON
    • Expiration: 30 days (tùy yêu cầu compliance)
    • Saved types: Chọn event types cần lưu (mặc định: ALL)
  4. Cấu hình Admin events settings:
    • Save events: ON
    • Include representation: ON (lưu request/response body)
  5. Event listeners: Thêm listeners cần thiết

2.2 Qua REST API

# Bật event logging cho realm
curl -X PUT "http://localhost:8080/admin/realms/my-realm" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eventsEnabled": true,
    "eventsExpiration": 2592000,
    "eventsListeners": ["jboss-logging"],
    "enabledEventTypes": [
        "LOGIN", "LOGIN_ERROR",
        "REGISTER", "REGISTER_ERROR",
        "LOGOUT",
        "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR",
        "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR",
        "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR",
        "UPDATE_PASSWORD",
        "RESET_PASSWORD",
        "SEND_RESET_PASSWORD"
    ],
    "adminEventsEnabled": true,
    "adminEventsDetailsEnabled": true
  }'

3. Event Listeners

Event Listeners xử lý events khi chúng xảy ra. Keycloak có sẵn các listeners sau:

3.1 jboss-logging Listener

Ghi events vào Keycloak server log (mặc định đã bật):

# Log output mẫu
2026-03-15 10:30:45,123 INFO  [org.keycloak.events] (executor-thread-1)
  type=LOGIN, realmId=my-realm, clientId=my-app, userId=abc-123,
  ipAddress=192.168.1.100, auth_method=openid-connect,
  auth_type=code, redirect_uri=https://myapp.com/callback,
  [email protected]

3.2 Email Listener

Gửi email cho user khi có events quan trọng (ví dụ: login từ thiết bị mới):

# Bật email listener
Realm Settings → Events → Event listeners → Thêm "email"

# Events được gửi email por defecto:
- LOGIN_ERROR (quá nhiều lần → cảnh báo compromised account)
- UPDATE_PASSWORD
- REMOVE_TOTP
- UPDATE_TOTP

4. Event Details và Event Store

4.1 Cấu trúc Login Event

{
    "time": 1710489045000,
    "type": "LOGIN",
    "realmId": "my-realm",
    "clientId": "my-web-app",
    "userId": "550e8400-e29b-41d4-a716-446655440000",
    "sessionId": "abc-session-id",
    "ipAddress": "192.168.1.100",
    "details": {
        "auth_method": "openid-connect",
        "auth_type": "code",
        "redirect_uri": "https://myapp.com/callback",
        "consent": "no_consent_required",
        "code_id": "xyz-code-id",
        "username": "[email protected]",
        "identity_provider": null
    }
}

4.2 Cấu trúc Admin Event

{
    "time": 1710489100000,
    "realmId": "my-realm",
    "authDetails": {
        "realmId": "master",
        "clientId": "security-admin-console",
        "userId": "admin-user-id",
        "ipAddress": "10.0.0.1"
    },
    "operationType": "CREATE",
    "resourceType": "USER",
    "resourcePath": "users/new-user-id",
    "representation": "{\"username\":\"newuser\",\"email\":\"[email protected]\",\"enabled\":true}"
}

4.3 Event Store — Database

Events được lưu trong database của Keycloak:

TableNội dung
EVENT_ENTITYLogin events
ADMIN_EVENT_ENTITYAdmin events

Lưu ý: Event store mặc định lưu trong DB Keycloak. Với lượng lớn events, nên sử dụng custom Event Listener để ship events ra hệ thống bên ngoài và set expiration ngắn cho built-in store.

5. Event Filtering và Truy vấn

5.1 Qua Admin Console

  1. Vào Events → tab User events hoặc Admin events
  2. Filter events theo:
    • Event type: LOGIN, LOGIN_ERROR, REGISTER...
    • Client: Chọn client cụ thể
    • User: Search theo user ID
    • Date range: From/To date
    • IP Address: Filter theo IP

5.2 Qua REST API — Login Events

# Lấy tất cả login events
curl -s "http://localhost:8080/admin/realms/my-realm/events" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

# Filter theo event type
curl -s "http://localhost:8080/admin/realms/my-realm/events?type=LOGIN_ERROR" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

# Filter theo user
curl -s "http://localhost:8080/admin/realms/my-realm/events?user=user-uuid" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

# Filter theo client và date range
curl -s "http://localhost:8080/admin/realms/my-realm/events?\
client=my-app&\
dateFrom=2026-03-01&\
dateTo=2026-03-31&\
first=0&\
max=100" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

# Filter nhiều event types
curl -s "http://localhost:8080/admin/realms/my-realm/events?\
type=LOGIN&type=LOGIN_ERROR&type=REGISTER" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

5.3 Qua REST API — Admin Events

# Lấy admin events
curl -s "http://localhost:8080/admin/realms/my-realm/admin-events" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

# Filter theo operation type
curl -s "http://localhost:8080/admin/realms/my-realm/admin-events?\
operationTypes=CREATE&\
resourceTypes=USER" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

# Filter theo resource path
curl -s "http://localhost:8080/admin/realms/my-realm/admin-events?\
resourcePath=users" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq .

6. Custom Event Listener SPI

Keycloak cho phép tạo custom Event Listener thông qua Service Provider Interface (SPI).

6.1 Tạo Maven Project

<!-- pom.xml -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>custom-event-listener</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <keycloak.version>26.1.0</keycloak.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
            <version>${keycloak.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi-private</artifactId>
            <version>${keycloak.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-services</artifactId>
            <version>${keycloak.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

6.2 Implement EventListenerProvider

// src/main/java/com/example/CustomEventListenerProvider.java
package com.example;

import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.jboss.logging.Logger;

import java.util.Map;

public class CustomEventListenerProvider implements EventListenerProvider {

    private static final Logger log = Logger.getLogger(CustomEventListenerProvider.class);
    private final KeycloakSession session;

    public CustomEventListenerProvider(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public void onEvent(Event event) {
        // Xử lý Login Events
        log.infof("Event: type=%s, realmId=%s, clientId=%s, userId=%s, ip=%s",
                event.getType(),
                event.getRealmId(),
                event.getClientId(),
                event.getUserId(),
                event.getIpAddress());

        // Xử lý theo event type
        switch (event.getType()) {
            case LOGIN:
                handleLogin(event);
                break;
            case LOGIN_ERROR:
                handleLoginError(event);
                break;
            case REGISTER:
                handleRegister(event);
                break;
            default:
                break;
        }
    }

    @Override
    public void onEvent(AdminEvent event, boolean includeRepresentation) {
        // Xử lý Admin Events
        log.infof("AdminEvent: operation=%s, resourceType=%s, resourcePath=%s, realmId=%s",
                event.getOperationType(),
                event.getResourceType(),
                event.getResourcePath(),
                event.getRealmId());

        if (event.getOperationType() == OperationType.DELETE) {
            handleAdminDelete(event);
        }
    }

    private void handleLogin(Event event) {
        // Ví dụ: Gửi event đến Kafka
        String payload = buildEventPayload(event);
        // kafkaProducer.send("keycloak-login-events", payload);
        log.debugf("Login event sent to message broker: %s", payload);
    }

    private void handleLoginError(Event event) {
        Map<String, String> details = event.getDetails();
        String username = details != null ? details.get("username") : "unknown";
        String error = event.getError();

        log.warnf("Login failure: user=%s, error=%s, ip=%s",
                username, error, event.getIpAddress());

        // Ví dụ: Increment metric counter cho monitoring
        // metricsService.incrementCounter("login_failures",
        //     "realm", event.getRealmId(),
        //     "error", error);
    }

    private void handleRegister(Event event) {
        log.infof("New user registered: userId=%s, realm=%s",
                event.getUserId(), event.getRealmId());
    }

    private void handleAdminDelete(AdminEvent event) {
        log.warnf("Admin DELETE operation: resource=%s/%s by admin=%s",
                event.getResourceType(),
                event.getResourcePath(),
                event.getAuthDetails().getUserId());
    }

    private String buildEventPayload(Event event) {
        // Tạo JSON payload cho message broker
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("\"type\":\"").append(event.getType()).append("\",");
        sb.append("\"realmId\":\"").append(event.getRealmId()).append("\",");
        sb.append("\"userId\":\"").append(event.getUserId()).append("\",");
        sb.append("\"clientId\":\"").append(event.getClientId()).append("\",");
        sb.append("\"ipAddress\":\"").append(event.getIpAddress()).append("\",");
        sb.append("\"time\":").append(event.getTime());
        sb.append("}");
        return sb.toString();
    }

    @Override
    public void close() {
        // Cleanup resources
    }
}

6.3 Implement EventListenerProviderFactory

// src/main/java/com/example/CustomEventListenerProviderFactory.java
package com.example;

import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;

public class CustomEventListenerProviderFactory implements EventListenerProviderFactory {

    public static final String PROVIDER_ID = "custom-event-listener";

    @Override
    public EventListenerProvider create(KeycloakSession session) {
        return new CustomEventListenerProvider(session);
    }

    @Override
    public void init(Config.Scope config) {
        // Đọc cấu hình từ keycloak config
        // Ví dụ: String kafkaBrokers = config.get("kafka-brokers", "localhost:9092");
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {
        // Post-initialization
    }

    @Override
    public void close() {
        // Cleanup
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
}

6.4 Đăng ký SPI

# src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory
com.example.CustomEventListenerProviderFactory

6.5 Deploy và Kích hoạt

# Build
mvn clean package

# Deploy
cp target/custom-event-listener-1.0.0.jar $KEYCLOAK_HOME/providers/
$KEYCLOAK_HOME/bin/kc.sh build

# Kích hoạt: Admin Console → Realm Settings → Events → Event listeners
# Thêm "custom-event-listener"

7. Keycloak JSON Logging

Để tích hợp với centralized logging, cấu hình Keycloak output JSON logs:

# Bật JSON logging
bin/kc.sh start \
  --log=console \
  --log-console-output=json

# Hoặc qua environment variables
KC_LOG=console
KC_LOG_CONSOLE_OUTPUT=json

7.1 JSON Log Output mẫu

{
    "timestamp": "2026-03-15T10:30:45.123Z",
    "level": "INFO",
    "loggerClassName": "org.keycloak.events",
    "loggerName": "org.keycloak.events",
    "message": "type=LOGIN, realmId=my-realm, clientId=my-app, userId=abc-123, ipAddress=192.168.1.100",
    "threadName": "executor-thread-1",
    "threadId": 42,
    "hostName": "keycloak-0",
    "processName": "keycloak",
    "processId": 1
}

7.2 Cấu hình Log Levels

# Cấu hình log levels cho events
bin/kc.sh start \
  --log=console \
  --log-console-output=json \
  --log-level=INFO \
  --log-level=org.keycloak.events:DEBUG

# Environment variables
KC_LOG_LEVEL=INFO
KC_LOG_LEVEL=org.keycloak.events:DEBUG

8. Tích hợp ELK Stack

Ship Keycloak logs đến ELK Stack (Elasticsearch, Logstash, Kibana) để phân tích tập trung.

8.1 Kiến trúc tổng quan

Keycloak (JSON logs)
    ↓
Filebeat (log shipper)
    ↓
Logstash (processing & enrichment)
    ↓
Elasticsearch (storage & indexing)
    ↓
Kibana (visualization & dashboards)

8.2 Filebeat Configuration

# filebeat.yml
filebeat.inputs:
  - type: container
    paths:
      - /var/log/containers/keycloak-*.log
    processors:
      - decode_json_fields:
          fields: ["message"]
          target: "keycloak"
          overwrite_keys: true
      - add_fields:
          target: ""
          fields:
            service.name: keycloak
            environment: production

output.logstash:
  hosts: ["logstash:5044"]

8.3 Logstash Pipeline

# logstash/pipeline/keycloak.conf
input {
  beats {
    port => 5044
  }
}

filter {
  if [service][name] == "keycloak" {
    # Parse Keycloak event message
    if [keycloak][message] =~ "^type=" {
      kv {
        source => "[keycloak][message]"
        field_split => ", "
        value_split => "="
        target => "kc_event"
      }

      mutate {
        add_field => {
          "event_type" => "%{[kc_event][type]}"
          "realm" => "%{[kc_event][realmId]}"
          "client_id" => "%{[kc_event][clientId]}"
        }
      }
    }

    # GeoIP enrichment
    if [kc_event][ipAddress] {
      geoip {
        source => "[kc_event][ipAddress]"
        target => "geo"
      }
    }

    # Detect suspicious patterns
    if [kc_event][type] == "LOGIN_ERROR" {
      mutate {
        add_tag => ["login_failure"]
      }
    }
  }
}

output {
  if [service][name] == "keycloak" {
    elasticsearch {
      hosts => ["elasticsearch:9200"]
      index => "keycloak-events-%{+YYYY.MM.dd}"
    }
  }
}

8.4 Kibana Dashboard

Tạo Kibana dashboards để monitor:

  • Login Success/Failure Rate — Bar chart theo thời gian
  • Top Login Errors — Pie chart theo error type
  • Login by Geo Location — Map visualization
  • Failed Logins by IP — Table phát hiện brute-force
  • User Registration Trend — Line chart theo ngày
  • Admin Operations Audit — Data table với full details

9. Tích hợp Grafana Loki

Grafana Loki là giải pháp log aggregation nhẹ hơn ELK, phù hợp cho Kubernetes environments.

9.1 Promtail Configuration

# promtail-config.yml
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: keycloak
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_label_app]
        regex: keycloak
        action: keep
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod
    pipeline_stages:
      - json:
          expressions:
            level: level
            logger: loggerName
            message: message
            timestamp: timestamp
      - labels:
          level:
          logger:
      - match:
          selector: '{app="keycloak"} |~ "type=LOGIN|type=REGISTER|type=LOGOUT"'
          stages:
            - regex:
                expression: 'type=(?P<event_type>\w+), realmId=(?P<realm>[\w-]+), clientId=(?P<client_id>[\w-]+), userId=(?P<user_id>[\w-]+)'
            - labels:
                event_type:
                realm:

9.2 Grafana Dashboard Queries

# Login failures trong 1 giờ qua
{app="keycloak"} |~ "type=LOGIN_ERROR" | json | count_over_time({app="keycloak"} |~ "LOGIN_ERROR" [1h])

# Login events theo realm
sum by (realm) (count_over_time({app="keycloak"} |~ "type=LOGIN" [5m]))

# Top IPs với login failures
{app="keycloak"} |~ "type=LOGIN_ERROR" | regexp `ipAddress=(?P<ip>[\d.]+)` | count by (ip) | sort desc | limit 10

10. SIEM Integration

Tích hợp Keycloak events với Security Information and Event Management (SIEM) systems.

10.1 Splunk Integration

# Cấu hình Filebeat ship đến Splunk HEC
output.logstash:
  enabled: false

output.http:
  enabled: true
  hosts: ["https://splunk-hec:8088"]
  path: "/services/collector/event"
  headers:
    Authorization: "Splunk <HEC_TOKEN>"
  format: json

10.2 SIEM Use Cases

Use CaseEvent PatternAction
Brute-force DetectionNhiều LOGIN_ERROR từ cùng IPAlert + Block IP
Account TakeoverLogin từ GeoIP bất thườngAlert + Require MFA
Privilege EscalationAdmin assign role adminAlert + Review
Data ExfiltrationNhiều token requests bất thườngAlert + Revoke sessions
Suspicious RegistrationNhiều REGISTER từ cùng IPAlert + CAPTCHA

11. Audit Compliance

11.1 SOC2 Requirements

SOC2 ControlKeycloak Implementation
CC6.1 — Logical access securityEvent logging cho LOGIN, LOGIN_ERROR, PASSWORD changes
CC6.2 — User authenticationMFA events, registration events
CC6.3 — Access authorizationAdmin Events cho role/permission changes
CC7.2 — Security monitoringReal-time alerting trên login failures
CC8.1 — Change managementAdmin Events với representations

11.2 HIPAA Requirements

HIPAA ControlKeycloak Implementation
§164.312(b) — Audit controlsBật tất cả event types, admin events với representations
§164.312(d) — Person authenticationEvent logging cho authentication attempts
§164.308(a)(5) — Security awarenessEmail notifications cho suspicious activity

11.3 Retention Policy

# Cấu hình event retention
# SOC2: minimum 1 năm
# HIPAA: minimum 6 năm

# Trong Keycloak (built-in store)
# Realm Settings → Events → Expiration: 365 days

# Trong Elasticsearch (centralized logging)
# ILM Policy:
# - Hot: 30 days (SSD)
# - Warm: 335 days (HDD)
# - Cold/Frozen: 5+ years (S3/GCS)
# - Delete: 7 years

12. Alert Automation

12.1 Prometheus Alerting

Keycloak expose metrics qua /metrics endpoint (cần bật metrics-enabled):

# Bật metrics
bin/kc.sh start --metrics-enabled=true
# prometheus-alerts.yml
groups:
  - name: keycloak-security
    rules:
      - alert: HighLoginFailureRate
        expr: |
          sum(rate(keycloak_login_error_total[5m])) by (realm)
          /
          sum(rate(keycloak_login_total[5m])) by (realm)
          > 0.3
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High login failure rate in realm {{ $labels.realm }}"
          description: "Login failure rate is {{ $value | humanizePercentage }} (threshold: 30%)"

      - alert: BruteForceDetected
        expr: |
          sum(increase(keycloak_login_error_total[5m])) by (realm) > 50
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Possible brute-force attack on realm {{ $labels.realm }}"
          description: "{{ $value }} login failures in 5 minutes"

      - alert: UnusualRegistrationSpike
        expr: |
          sum(increase(keycloak_registrations_total[10m])) by (realm) > 100
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Unusual registration spike in realm {{ $labels.realm }}"

12.2 Alertmanager Routing

# alertmanager.yml
route:
  receiver: default
  routes:
    - match:
        severity: critical
      receiver: pagerduty-security
      continue: true
    - match:
        severity: critical
      receiver: slack-security
    - match:
        severity: warning
      receiver: slack-ops

receivers:
  - name: default
    email_configs:
      - to: [email protected]

  - name: slack-security
    slack_configs:
      - api_url: https://hooks.slack.com/services/xxx
        channel: '#security-alerts'
        title: '{{ .GroupLabels.alertname }}'
        text: '{{ .CommonAnnotations.description }}'

  - name: pagerduty-security
    pagerduty_configs:
      - service_key: <pagerduty-integration-key>
        severity: critical

13. Best Practices

  • Bật cả Login Events và Admin Events — Không bỏ sót bất kỳ hoạt động nào trong hệ thống.
  • Ship events ra external system — Không chỉ dựa vào built-in event store. Sử dụng ELK/Loki/SIEM cho long-term storage.
  • Bật admin event representations — Lưu request/response body cho admin operations để audit đầy đủ.
  • Set appropriate retention — Tuân thủ compliance requirements (SOC2: 1 năm, HIPAA: 6 năm).
  • Monitor login failure rates — Set alerts cho brute-force detection và account takeover.
  • Correlate events — Kết hợp Keycloak events với application logs để có bức tranh toàn diện.
  • Protect event logs — Log data chứa PII, cần mã hóa at rest và in transit, hạn chế access.