# 通知設計書 5-パスワードリトライ制限超過通知

## 概要

本ドキュメントは、RuoYiシステムにおけるパスワードリトライ制限超過通知（ログ記録）機能の設計仕様を記述したものである。パスワード入力失敗がmaxRetryCount回を超えた場合にログを記録し、UserPasswordRetryLimitExceedExceptionをスローする機能について定義する。

### 本通知の処理概要

本通知は、同一ユーザーが連続してパスワード入力を失敗した回数がシステム設定値（maxRetryCount）を超過した際に、ログを記録し、一定期間アカウントをロックする機能を提供する。ブルートフォース攻撃対策として重要なセキュリティ機能である。

**業務上の目的・背景**：パスワード総当たり攻撃（ブルートフォース攻撃）からアカウントを保護するため、連続失敗回数に制限を設ける必要がある。攻撃者が無限にパスワードを試行することを防止し、正規ユーザーのアカウント保護を実現する。

**通知の送信タイミング**：ユーザーがパスワード入力に失敗し、その失敗回数がmaxRetryCount設定値を超過した時点でログ記録が実行される。SysPasswordService.validate()メソッド内でリトライカウントがチェックされる。

**通知の受信者**：システム管理者（ログ閲覧権限を持つユーザー）。記録されたログは監視画面から参照可能。また、攻撃対象となったユーザー自身もログインできなくなることで間接的に通知される。

**通知内容の概要**：リトライ制限超過時のユーザー名、IPアドレス、ログイン地点、使用ブラウザ、OS情報、制限超過メッセージ、試行日時が記録される。

**期待されるアクション**：管理者は制限超過ログを監視し、攻撃パターンを検知する。必要に応じて該当IPのブロック、セキュリティチームへのエスカレーション、ユーザーへの通知などを行う。正規ユーザーがロックされた場合は、キャッシュのクリアまたは一定時間経過後に自動解除される。

## 通知種別

ログ記録（データベース監査ログ） + アカウント一時ロック

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（AsyncManager経由） |
| 優先度 | 最高（セキュリティ重大イベント） |
| リトライ | 無（非同期タスクのため） |

### 送信先決定ロジック

- ログは一律でsys_logininforテーブルに記録
- リトライカウントはキャッシュ（loginRecordCache）で管理

## 通知テンプレート

### ログ記録の場合

| 項目 | 内容 |
|-----|------|
| 記録先 | sys_logininforテーブル |
| 記録形式 | データベースレコード |

### 本文テンプレート

```
ログインユーザー: {loginName}
IPアドレス: {ipaddr}
ログイン地点: {loginLocation}
ブラウザ: {browser}
OS: {os}
ステータス: 失敗
メッセージ: 密码输入错误{maxRetryCount}次，帐户锁定{lockTime}分钟
ログイン日時: {loginTime}
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| - | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| loginName | ログイン試行ユーザー名 | user.getLoginName() | Yes |
| maxRetryCount | 最大リトライ回数 | user.password.maxRetryCount設定 | Yes |
| ipaddr | IPアドレス | ShiroUtils.getIp() | Yes |
| loginLocation | ログイン地点 | AddressUtils.getRealAddressByIP() | No |
| browser | ブラウザ情報 | UserAgentUtils.getBrowser() | No |
| os | OS情報 | UserAgentUtils.getOperatingSystem() | No |
| status | ログインステータス | Constants.FAIL (1) | Yes |
| msg | 制限超過メッセージ | MessageUtils.message() | Yes |
| loginTime | 試行日時 | 現在日時（自動） | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| サービス処理 | パスワード検証 | retryCount > maxRetryCount | リトライカウントが上限超過 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| retryCount <= maxRetryCount | リトライカウントが上限以内 |
| パスワード一致 | パスワードが正しい場合はカウントクリア |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[パスワード検証リクエスト] --> B[SysPasswordService.validate]
    B --> C[キャッシュからretryCount取得]
    C --> D{retryCount == null?}
    D -->|Yes| E[retryCount = 0で初期化]
    D -->|No| F[retryCount取得]
    E --> G[retryCount++]
    F --> G
    G --> H{retryCount > maxRetryCount?}
    H -->|Yes| I[AsyncManager.execute]
    H -->|No| J{パスワード一致?}
    I --> K[AsyncFactory.recordLogininfor]
    K --> L[sys_logininforにINSERT]
    L --> M[UserPasswordRetryLimitExceedException]
    J -->|No| N[retryCountをキャッシュ更新]
    J -->|Yes| O[retryCountクリア]
    N --> P[UserPasswordNotMatchException]
    O --> Q[検証成功]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| - | - | データベースは直接参照しない（キャッシュ使用） |

### キャッシュ参照

| キャッシュ名 | 用途 | 構造 |
|------------|------|------|
| loginRecordCache | ユーザーごとのリトライカウント管理 | Map<String, AtomicInteger> |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| sys_logininfor | INSERT | リトライ制限超過情報記録 |

#### sys_logininfor テーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | info_id | 自動採番 | 主キー |
| INSERT | login_name | ユーザー名 | - |
| INSERT | ipaddr | IPアドレス | ShiroUtils.getIp() |
| INSERT | login_location | ログイン地点 | IP逆引き |
| INSERT | browser | ブラウザ | User-Agentから解析 |
| INSERT | os | OS | User-Agentから解析 |
| INSERT | status | 1 | 失敗 |
| INSERT | msg | 制限超過メッセージ | maxRetryCount含む |
| INSERT | login_time | 現在日時 | - |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| キャッシュアクセス失敗 | キャッシュ接続エラー | デフォルト値使用 |
| DB接続エラー | データベース接続不可 | ログ出力のみ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（非同期タスクのため） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし。パスワード検証に応じて常時記録。

## セキュリティ考慮事項

- **ブルートフォース攻撃対策**：連続失敗回数を制限することで、パスワード総当たり攻撃を防止
- **キャッシュベース管理**：リトライカウントをキャッシュで管理し、高速な判定を実現
- **自動解除**：キャッシュのTTL設定により、一定時間後に自動的にロック解除
- **パスワード成功時クリア**：正しいパスワード入力時にリトライカウントをクリア
- **設定可能な閾値**：maxRetryCountはapplication.ymlで設定可能

## 備考

- maxRetryCountは`user.password.maxRetryCount`プロパティで設定
- キャッシュ名はShiroConstants.LOGIN_RECORD_CACHEで定義
- キャッシュのTTL（ロック時間）はehcache設定で定義
- リトライカウントはAtomicIntegerでスレッドセーフに管理

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

リトライカウント管理に使用されるキャッシュ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ShiroConstants.java | `ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java` | キャッシュ名定義（LOGIN_RECORD_CACHE） |

**読解のコツ**: キャッシュのキー（ログイン名）とバリュー（AtomicInteger）の関係を理解する。

#### Step 2: エントリーポイントを理解する

パスワード検証サービスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SysPasswordService.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java` | パスワード検証、リトライカウント管理 |

**主要処理フロー**:
1. **33-34行目**: maxRetryCount設定読み込み - `@Value("${user.password.maxRetryCount}")`
2. **36-40行目**: キャッシュ初期化 - `loginRecordCache = cacheManager.getCache(ShiroConstants.LOGIN_RECORD_CACHE)`
3. **42-69行目**: validate()メソッド - パスワード検証メイン処理
4. **46-52行目**: リトライカウント取得/初期化
5. **53-57行目**: **リトライ制限超過チェックとログ記録** - `if (retryCount.incrementAndGet() > Integer.valueOf(maxRetryCount).intValue())`
6. **55行目**: ログ記録 - `AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount))`
7. **56行目**: 例外スロー - `throw new UserPasswordRetryLimitExceedException(Integer.valueOf(maxRetryCount).intValue())`
8. **59-64行目**: パスワード不一致時の処理
9. **65-68行目**: パスワード一致時のカウントクリア

#### Step 3: 例外クラスを理解する

制限超過例外クラスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | UserPasswordRetryLimitExceedException.java | `ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java` | 例外クラス定義 |

#### Step 4: 非同期ログ記録処理を理解する

ログ記録の非同期処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AsyncFactory.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java` | 非同期タスク生成 |

### プログラム呼び出し階層図

```
SysLoginService.login() (ログイン処理)
    │
    └─ SysPasswordService.validate() (パスワード検証)
           │
           ├─ loginRecordCache.get(loginName) (リトライカウント取得)
           │
           ├─ retryCount.incrementAndGet() (カウントインクリメント)
           │
           ├─ リトライ制限超過判定
           │      │
           │      └─ AsyncManager.execute(recordLogininfor) ─▶ sys_logininfor
           │             │
           │             └─ UserPasswordRetryLimitExceedException
           │
           ├─ matches(user, password) (パスワード照合)
           │      │
           │      ├─ 不一致 ─▶ loginRecordCache.put() ─▶ UserPasswordNotMatchException
           │      │
           │      └─ 一致 ─▶ clearLoginRecordCache() ─▶ 成功
           │
           └─ encryptPassword() (パスワードハッシュ化)
```

### データフロー図

```
[入力]               [処理]                    [出力]

パスワード ───▶ SysPasswordService ───▶ 検証結果
    │                    │
    │                    ▼
    │              loginRecordCache
    │              (リトライカウント)
    │                    │
    │                    ▼
    │              制限超過判定
    │                    │
    │                    ├─ 超過時 ─▶ sys_logininfor ─▶ 例外
    │                    │
    │                    └─ 範囲内 ─▶ パスワード照合
    │
    └─────────────▶ キャッシュ更新/クリア
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SysPasswordService.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java` | ソース | パスワード検証サービス |
| UserPasswordRetryLimitExceedException.java | `ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java` | ソース | リトライ制限超過例外 |
| UserPasswordRetryLimitCountException.java | `ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java` | ソース | リトライカウント例外 |
| ShiroConstants.java | `ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java` | ソース | キャッシュ名定義 |
| AsyncFactory.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java` | ソース | 非同期タスクファクトリ |
| SysLogininfor.java | `ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java` | ソース | ログイン情報エンティティ |
| application.yml | `ruoyi-admin/src/main/resources/application.yml` | 設定 | maxRetryCount設定 |
| ehcache-shiro.xml | `ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml` | 設定 | キャッシュTTL設定 |
