SecurityEvent 実装ガイド
このドキュメントの目的
SecurityEventの仕組みとリトライ戦略を理解することが目標です。
所要時間
⏱️ 約15分
前提知識
- 04. Authentication実装
- Spring Frameworkの
@EventListenerの基礎知識
SecurityEventとは
目的: 「何が起きたか」を記録・通知
認証成功 → SecurityEvent(password_success) → 監査ログに記録 → 外部サービスに通知
特徴:
- ✅ 記録中心: security_eventテーブルに永久保存
- ✅ 監視・通知: Security Event Hooksで外部サービスに送信
- ❌ 状態変更しない: ユーザー・トークン等は変更しない
処理フロー概要
SecurityEventの処理は以下の流れで行われます:
- イベント発行: EntryServiceが
eventPublisher.publish()で発行(同期) - 非同期処理: Spring
@EventListenerがイベントを受信し、ThreadPoolに投入 - イベント処理:
SecurityEventHandlerがDBへの記録とHook送信を実行 - リトライ: ThreadPool満杯時は
SecurityEventRetrySchedulerが後で再実行
EntryService → publish() → ThreadPool → SecurityEventHandler → DB記録 + Hook送信
↓ (満杯時)
RetryScheduler → 60秒後に再実行(最大3回)
ポイント: イベント発行は同期だが、処理は非同期。API呼び出しはイベント処理完了を待たずに返却される。
アーキテクチャ
Application Plane API(認証・認可・トークン発行等)
↓
EntryService - eventPublisher.publish()
↓ (同期)
┌─────────────────────────────────────────────────────┐
│ SecurityEventPublisher(インターフェース) │
├─────────────────────────────────────────────────────┤
│ SecurityEventPublisherService(Adapter層) │
│ → applicationEventPublisher.publishEvent() │
│ (Spring ApplicationEventPublisher) │
└─────────────────────────────────────────────────────┘
↓ (同 期で即座に返却)
EntryService処理完了 → HTTPレスポンス返却
↓
↓ (非同期 - Spring @EventListener)
↓
┌─────────────────────────────────────────────────────┐
│ SecurityEventListener(Spring Bean) │
├─────────────────────────────────────────────────────┤
│ @EventListener │
│ handleSecurityEvent(SecurityEvent event) │
│ ↓ │
│ SecurityEventRunnable作成 │
│ - TenantLoggingContext設定 │
│ - SecurityEventHandler呼び出し │
│ ↓ │
│ securityEventTaskExecutor.execute(runnable) │
│ → ThreadPoolに投入 │
└─────────────────────────────────────────────────────┘
↓ (ThreadPoolで非同期実行)
├─ 正常時: 別スレッドで実行
└─ ThreadPool満杯時: RejectedExecutionHandler
↓
┌─────────────────────────────────────────────────┐
│ RejectedExecutionHandler │
├─────────────────────────────────────────────────┤
│ SecurityEventRetryScheduler.enqueue() │
│ → retryQueueに追加 │
└─────────────────────────────────────────────────┘
↓ (別スレッド - 正常実行時)
┌─────────────────────────────────────────────────────┐
│ SecurityEventHandler(Platform層) │
├─────────────────────────────────────────────────────┤
│ 1. SecurityEventLogService.logEvent() │
│ → security_event テーブルに記録 │
│ │
│ 2. updateStatistics() ※statistics_enabled時のみ │
│ → テナント統計データを更新(DAU/MAU/YAU等) │
│ │
│ 3. SecurityEventHookConfiguration取得 │
│ → 設定されたHookを取得(キャッシュ使用、TTL 5分) │
│ │
│ 4. SecurityEventHook.shouldExecute() │
│ → イベントタイプフィルタリング │
│ │
│ 5. SecurityEventHook.execute() │
│ → 外部サービスに送信(Webhook/Slack/SIEM) │
│ │
│ 6. SecurityEventHookResult保存 │
│ → security_event_hook_results テーブル │
└────────────────────────────── ───────────────────────┘
実装:
- Publisher: SecurityEventPublisherService.java
- Runnable: SecurityEventRunnable.java
- Handler: SecurityEventHandler.java
- Retry Scheduler: SecurityEventRetryScheduler.java
- ThreadPool設定: AsyncConfig.java:46-69
同期処理と非同期処理
SecurityEventの発行には 非同期(publish) と 同期(publishSync) の2つのモードがあります。
使い分け
| モード | メソッド | 用途 |
|---|---|---|
| 非同期 | publish() | Application Plane(認証・認可・トークン発行等) |
| 同期 | publishSync() | Control Plane(ユーザー作成・更新・削除等の管理API) |
非同期: APIレスポンスを高速に返したい場合。イベント処理の成否はレスポンスに影響しない。
同期: イベント処理(ログ保存、統計更新、フック実行)が完了してからレスポンスを返したい場合。管理APIではイベント処理の完了を保証する必要がある。
スレッドとトランザクションの違い
【非同期: publish()】
リクエストスレッド イベント処理スレッド
────────────────── ──────────────────
ManagementEntryServiceProxy SecurityEventListerService
│ beginTransaction() │
│ ┌─────────────────────┐ │
│ │ Transaction A │ │
│ │ │ │
│ │ UserCreationService │ │
│ │ register(user) │ │
│ │ publish(event) ───────────→ @Async @EventListener
│ │ │ │ beginTransaction()
│ │ │ │ ┌──────────────────┐
│ └─────────────────────┘ │ │ Transaction B │
│ commitTransaction() │ │ │
│ │ │ SecurityEvent │
↓ HTTPレスポンス返却 │ │ Handler.handle() │
(イベント処理完了を待たない) │ └──────────────────┘
│ commitTransaction()
↓
【同期: publishSync()】
リクエストスレッド(同一スレッドで全処理)
──────────────────────────────────────
ManagementEntryServiceProxy
│ beginTransaction()
│ ┌───────────────────────────────┐
│ │ Transaction A │
│ │ │
│ │ UserCreationService │
│ │ register(user) │
│ │ publishSync(event) │
│ │ │ │
│ │ ↓ │
│ │ rawSecurityEventApi │
│ │ .handle() │
│ │ │ │
│ │ ↓ │
│ │ SecurityEventHandler │
│ │ .handle() │
│ │ - logEvent() │
│ │ - updateStatistics() │
│ │ - executeHooks() │
│ │ │
│ └───────────────────────────────┘
│ commitTransaction()
↓ HTTPレスポンス返却
(イベント処理完了後に返す)
| 項目 | 非同期(publish) | 同期(publishSync) |
|---|---|---|
| スレッド | 別スレッド(TaskExecutor経由) | 同一スレッド |