Spring Securityは、Servlet Filterの仕組みを活用してセキュリティ機能を実現しています。本記事では、DelegatingFilterProxy、FilterChainProxy、SecurityFilterChainの構造と、主要なSecurityFilterの役割・実行順序を詳しく解説します。これらの内部構造を理解することで、カスタムフィルターの追加やトラブルシューティングが容易になります。
実行環境と前提条件
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 | バージョン・要件 |
|---|---|
| Java | 17以上 |
| Spring Boot | 3.4.x |
| Spring Security | 6.4.x |
| ビルドツール | Maven または Gradle |
| IDE | VS Code または IntelliJ IDEA |
事前に以下の知識があると理解がスムーズです。
- Servlet Filterの基本概念
- Spring Securityの基本的な設定方法
- Spring Bootプロジェクトの構成
Servlet Filterの基本概念
Spring SecurityのFilterChainアーキテクチャを理解するために、まずServlet Filterの基本を確認します。
FilterChainの仕組み
ServletコンテナはHTTPリクエストを処理する際、FilterChainを通じて複数のFilterを順番に実行します。各Filterは、リクエストの前処理・後処理を行い、次のFilterまたはServletに処理を委譲します。
flowchart LR
A[クライアント] --> B[Filter 1]
B --> C[Filter 2]
C --> D[Filter N]
D --> E[DispatcherServlet]
E --> F[Controller]
F --> E
E --> D
D --> C
C --> B
B --> AFilterの基本的な実装は以下のようになります。
|
|
Filterは以下の機能を持ちます。
| 機能 | 説明 |
|---|---|
| リクエストの前処理 | 認証、ログ記録、リクエスト変換など |
| 処理の委譲 | chain.doFilter()で次の処理へ |
| レスポンスの後処理 | ログ記録、レスポンス変換など |
| 処理の中断 | 条件によりServletに到達させない |
Filterの実行順序は非常に重要です。Spring Securityでは、認証Filterが認可Filterより前に実行される必要があります。
Spring SecurityのFilterアーキテクチャ
Spring Securityは、3つの主要コンポーネントを組み合わせてセキュリティ機能を実現しています。
flowchart TB
A[HTTPリクエスト] --> B[Servlet Container<br/>FilterChain]
B --> C[DelegatingFilterProxy]
C --> D[FilterChainProxy]
D --> E{RequestMatcher}
E -->|"/api/**"| F[SecurityFilterChain 0]
E -->|"/**"| G[SecurityFilterChain N]
F --> H[Security Filters]
G --> I[Security Filters]
H --> J[DispatcherServlet]
I --> JDelegatingFilterProxyの役割
DelegatingFilterProxyは、ServletコンテナとSpringのApplicationContextを橋渡しする重要なコンポーネントです。
ServletコンテナはServlet仕様に基づいてFilterを管理しますが、Spring Beanを直接認識できません。DelegatingFilterProxyは、ServletコンテナにFilterとして登録され、実際の処理をSpring Beanに委譲します。
flowchart LR
A[Servlet Container] --> B[DelegatingFilterProxy]
B --> C[Spring ApplicationContext]
C --> D["FilterChainProxy<br/>(Spring Bean)"]DelegatingFilterProxyの擬似コードは以下のようになります。
|
|
DelegatingFilterProxyの主な特徴は以下のとおりです。
| 特徴 | 説明 |
|---|---|
| 遅延初期化 | Spring Beanのルックアップをリクエスト時まで遅延 |
| ライフサイクル分離 | ServletコンテナとSpringコンテナのライフサイクルを分離 |
| 透過的な委譲 | Servletコンテナから見ると通常のFilterとして動作 |
FilterChainProxyの役割
FilterChainProxyは、Spring Securityの中核となる特別なFilterです。DelegatingFilterProxyから委譲を受け、複数のSecurityFilterChainを管理します。
flowchart TB
A[DelegatingFilterProxy] --> B[FilterChainProxy]
B --> C{RequestMatcher判定}
C -->|マッチ| D[SecurityFilterChain 0]
C -->|マッチ| E[SecurityFilterChain 1]
C -->|マッチ| F[SecurityFilterChain N]
D --> G[Security Filters実行]
E --> H[Security Filters実行]
F --> I[Security Filters実行]FilterChainProxyが提供する主要な機能は以下のとおりです。
| 機能 | 説明 |
|---|---|
| SecurityFilterChainの選択 | リクエストURLに基づいて適切なチェーンを選択 |
| セキュリティコンテキストのクリア | メモリリークを防ぐためにSecurityContextをクリア |
| HttpFirewallの適用 | 悪意のあるリクエストからアプリケーションを保護 |
| デバッグポイント | Spring Securityのデバッグ開始点として最適 |
FilterChainProxyは最初にマッチしたSecurityFilterChainのみを実行します。以下の設定では、/api/**へのリクエストはapiFilterChainのみが適用されます。
|
|
SecurityFilterChainの構造
SecurityFilterChainは、特定のリクエストパターンに対して適用されるセキュリティフィルターのリストを保持します。
|
|
Spring Boot環境では、HttpSecurityビルダーを使用してSecurityFilterChainを構成します。
|
|
主要なSecurityFilterの役割と実行順序
Spring Securityには多数のFilterが存在し、それぞれ特定の役割を担います。Filterは厳密に定義された順序で実行されます。
デフォルトのFilter実行順序
以下は、Spring Security 6.4における主要なFilterの実行順序です。
| 順序 | Filter名 | 役割 |
|---|---|---|
| 1 | DisableEncodeUrlFilter | URLエンコーディングを無効化 |
| 2 | WebAsyncManagerIntegrationFilter | 非同期処理のセキュリティコンテキスト統合 |
| 3 | SecurityContextHolderFilter | SecurityContext のライフサイクル管理 |
| 4 | HeaderWriterFilter | セキュリティ関連ヘッダーの追加 |
| 5 | CsrfFilter | CSRF攻撃からの保護 |
| 6 | LogoutFilter | ログアウト処理 |
| 7 | UsernamePasswordAuthenticationFilter | フォームログイン認証 |
| 8 | DefaultLoginPageGeneratingFilter | デフォルトログインページ生成 |
| 9 | DefaultLogoutPageGeneratingFilter | デフォルトログアウトページ生成 |
| 10 | BasicAuthenticationFilter | HTTP Basic認証 |
| 11 | RequestCacheAwareFilter | 認証後のリクエスト復元 |
| 12 | SecurityContextHolderAwareRequestFilter | Servlet API統合 |
| 13 | AnonymousAuthenticationFilter | 匿名ユーザー認証情報の設定 |
| 14 | ExceptionTranslationFilter | セキュリティ例外のHTTPレスポンス変換 |
| 15 | AuthorizationFilter | アクセス権限のチェック |
flowchart TB
subgraph "セキュリティコンテキスト管理"
A[SecurityContextHolderFilter]
end
subgraph "攻撃対策"
B[HeaderWriterFilter]
C[CsrfFilter]
end
subgraph "認証処理"
D[LogoutFilter]
E[UsernamePasswordAuthenticationFilter]
F[BasicAuthenticationFilter]
G[AnonymousAuthenticationFilter]
end
subgraph "例外処理・認可"
H[ExceptionTranslationFilter]
I[AuthorizationFilter]
end
A --> B --> C --> D --> E --> F --> G --> H --> ISecurityContextHolderFilter
SecurityContextHolderFilterは、リクエスト処理の最初期にSecurityContextを設定し、処理完了後にクリアします。
sequenceDiagram
participant Client
participant Filter as SecurityContextHolderFilter
participant Context as SecurityContextHolder
participant Chain as FilterChain
Client->>Filter: リクエスト
Filter->>Context: SecurityContext読み込み
Filter->>Chain: 処理委譲
Chain-->>Filter: 処理完了
Filter->>Context: SecurityContextクリア
Filter-->>Client: レスポンスSecurityContextには、現在の認証情報(Authenticationオブジェクト)が格納されます。
|
|
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilterは、フォームログインリクエストを処理します。デフォルトではPOST /loginへのリクエストを処理します。
sequenceDiagram
participant Client
participant Filter as UsernamePasswordAuthenticationFilter
participant Manager as AuthenticationManager
participant Provider as AuthenticationProvider
participant Handler as AuthenticationSuccessHandler
Client->>Filter: POST /login<br/>(username, password)
Filter->>Filter: UsernamePasswordAuthenticationToken作成
Filter->>Manager: authenticate()
Manager->>Provider: authenticate()
Provider-->>Manager: Authentication
Manager-->>Filter: Authentication
Filter->>Handler: onAuthenticationSuccess()
Handler-->>Client: リダイレクト(成功ページへ)主な処理フローは以下のとおりです。
HttpServletRequestからユーザー名とパスワードを抽出UsernamePasswordAuthenticationTokenを作成AuthenticationManagerに認証を委譲- 認証成功時は
AuthenticationSuccessHandlerを呼び出し - 認証失敗時は
AuthenticationFailureHandlerを呼び出し
|
|
BasicAuthenticationFilter
BasicAuthenticationFilterは、HTTP Basic認証ヘッダーを処理します。
sequenceDiagram
participant Client
participant Filter as BasicAuthenticationFilter
participant Manager as AuthenticationManager
participant EntryPoint as BasicAuthenticationEntryPoint
Client->>Filter: リクエスト<br/>(Authorization: Basic xxx)
Filter->>Filter: Base64デコード
Filter->>Manager: authenticate()
alt 認証成功
Manager-->>Filter: Authentication
Filter->>Filter: SecurityContextに設定
Filter-->>Client: 処理継続
else 認証失敗
Manager-->>Filter: AuthenticationException
Filter->>EntryPoint: commence()
EntryPoint-->>Client: 401 + WWW-Authenticate
endHTTP Basic認証の特徴は以下のとおりです。
| 特徴 | 説明 |
|---|---|
| ステートレス | セッションを使用しない |
| シンプル | リクエストごとに認証情報を送信 |
| 毎回認証 | 各リクエストでAuthenticationManagerを呼び出し |
| HTTPS必須 | 認証情報がBase64エンコードのみのため |
|
|
CsrfFilter
CsrfFilterは、クロスサイトリクエストフォージェリ(CSRF)攻撃からアプリケーションを保護します。
flowchart TB
A[HTTPリクエスト] --> B{HTTPメソッド}
B -->|GET, HEAD, TRACE, OPTIONS| C[CSRF検証スキップ]
B -->|POST, PUT, DELETE, PATCH| D{CSRFトークン検証}
D -->|有効| E[処理継続]
D -->|無効/なし| F[AccessDeniedException]
C --> G[次のFilterへ]
E --> G
F --> H[403 Forbidden]CSRF保護の設定オプションは以下のとおりです。
|
|
ExceptionTranslationFilter
ExceptionTranslationFilterは、AuthenticationExceptionとAccessDeniedExceptionをHTTPレスポンスに変換します。
flowchart TB
A[ExceptionTranslationFilter] --> B[FilterChain.doFilter]
B --> C{例外発生?}
C -->|AuthenticationException| D[AuthenticationEntryPoint]
C -->|AccessDeniedException| E{認証済み?}
C -->|例外なし| F[正常終了]
E -->|未認証| D
E -->|認証済み| G[AccessDeniedHandler]
D --> H[401 Unauthorized<br/>またはログインページへリダイレクト]
G --> I[403 Forbidden]ExceptionTranslationFilterの擬似コードは以下のようになります。
|
|
AuthorizationFilter
AuthorizationFilterは、認可(アクセス制御)を担当する最後のセキュリティフィルターです。
flowchart TB
A[AuthorizationFilter] --> B[AuthorizationManager]
B --> C{認可チェック}
C -->|許可| D[処理継続]
C -->|拒否| E[AccessDeniedException]
E --> F[ExceptionTranslationFilterで処理]AuthorizationFilterは、設定された認可ルールに基づいてアクセス可否を判定します。
|
|
カスタムFilterの追加方法
Spring Securityでは、独自のFilterをSecurityFilterChainに追加できます。
Filterの追加位置
HttpSecurityは、Filterを追加するための3つのメソッドを提供します。
| メソッド | 説明 |
|---|---|
addFilterBefore(Filter, Class) |
指定したFilterの前に追加 |
addFilterAfter(Filter, Class) |
指定したFilterの後に追加 |
addFilterAt(Filter, Class) |
指定したFilterの位置に追加(置換) |
カスタムFilterの実装例
以下は、テナントIDを検証するカスタムFilterの例です。
|
|
SecurityFilterChainへの追加
カスタムFilterを適切な位置に追加します。認証済みユーザーの情報が必要な場合は、認証Filterの後に配置します。
|
|
FilterをSpring Beanとして登録する場合の注意点
FilterをSpring Bean(@Componentなど)として宣言すると、Spring BootがServletコンテナにも自動登録してしまい、Filterが2回実行される問題が発生します。
この問題を回避するには、FilterRegistrationBeanを使用して自動登録を無効化します。
|
|
複数SecurityFilterChainの設定
APIとWebアプリケーションで異なるセキュリティ設定が必要な場合、複数のSecurityFilterChainを定義できます。
|
|
@Orderアノテーションで優先順位を指定し、数値が小さいほど先にマッチングが試行されます。
デバッグとトラブルシューティング
Spring Securityの動作を詳細に確認する方法を解説します。
ログレベルの設定
application.propertiesに以下を追加すると、FilterChainの実行順序や認証・認可の判定結果が出力されます。
|
|
出力例は以下のとおりです。
|
|
登録されたFilterの一覧を確認
アプリケーション起動時のDEBUGログで、登録されたFilterの一覧を確認できます。
|
|
一般的な問題と解決方法
以下は、よく遭遇する問題とその解決方法です。
| 問題 | 原因 | 解決方法 |
|---|---|---|
| 403 Forbidden(CSRFエラー) | CSRFトークンが送信されていない | フォームにCSRFトークンを含めるか、API向けにCSRFを無効化 |
| 401 Unauthorized(認証エラー) | 認証情報が不正または未送信 | Authorization ヘッダーや認証情報を確認 |
| カスタムFilterが2回実行される | FilterがSpring Beanかつ自動登録されている | FilterRegistrationBeanで無効化 |
| Filterの順序が期待と異なる | addFilterBefore/Afterの指定が不正 |
正しい基準Filterを指定 |
まとめ
本記事では、Spring SecurityのFilterChainアーキテクチャについて詳しく解説しました。
要点を整理すると以下のとおりです。
- DelegatingFilterProxyはServletコンテナとSpring ApplicationContextを橋渡しする
- FilterChainProxyは複数のSecurityFilterChainを管理し、リクエストに応じて適切なチェーンを選択する
- SecurityFilterChainは特定のURLパターンに対するセキュリティフィルターのリストを定義する
- 主要なFilterにはSecurityContextHolderFilter、UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、ExceptionTranslationFilter、AuthorizationFilterがある
- カスタムFilterは
addFilterBefore/addFilterAfter/addFilterAtで適切な位置に追加する - 複数の
SecurityFilterChainを@Orderで優先順位を指定して定義できる
これらの内部構造を理解することで、Spring Securityのカスタマイズやデバッグが効率的に行えるようになります。