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 --> A

Filterの基本的な実装は以下のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;

public class SampleFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // リクエスト前処理
        System.out.println("Before request processing");
        
        // 次のFilterまたはServletに処理を委譲
        chain.doFilter(request, response);
        
        // レスポンス後処理
        System.out.println("After request processing");
    }
}

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 --> J

DelegatingFilterProxyの役割

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の擬似コードは以下のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class DelegatingFilterProxy implements Filter {
    
    private Filter delegate;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // Spring BeanとしてのFilterを取得(遅延初期化)
        Filter delegateFilter = getFilterBean("springSecurityFilterChain");
        
        // 実際の処理をSpring Beanに委譲
        delegateFilter.doFilter(request, response, chain);
    }
}

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のみが適用されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Configuration
@EnableWebSecurity
public class MultiFilterChainConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**")
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());
        
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        
        return http.build();
    }
}

SecurityFilterChainの構造

SecurityFilterChainは、特定のリクエストパターンに対して適用されるセキュリティフィルターのリストを保持します。

1
2
3
4
5
6
7
8
public interface SecurityFilterChain {
    
    // このチェーンがリクエストにマッチするかを判定
    boolean matches(HttpServletRequest request);
    
    // 適用するFilterのリストを取得
    List<Filter> getFilters();
}

Spring Boot環境では、HttpSecurityビルダーを使用してSecurityFilterChainを構成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(Customizer.withDefaults())
        .authorizeHttpRequests(authorize -> authorize
            .anyRequest().authenticated()
        )
        .formLogin(Customizer.withDefaults())
        .httpBasic(Customizer.withDefaults());
    
    return http.build();
}

主要な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 --> I

SecurityContextHolderFilter

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オブジェクト)が格納されます。

1
2
3
4
// SecurityContextHolderからの認証情報取得例
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

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: リダイレクト(成功ページへ)

主な処理フローは以下のとおりです。

  1. HttpServletRequestからユーザー名とパスワードを抽出
  2. UsernamePasswordAuthenticationTokenを作成
  3. AuthenticationManagerに認証を委譲
  4. 認証成功時はAuthenticationSuccessHandlerを呼び出し
  5. 認証失敗時はAuthenticationFailureHandlerを呼び出し
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// UsernamePasswordAuthenticationFilterの設定例
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .formLogin(form -> form
            .loginPage("/custom-login")           // カスタムログインページ
            .loginProcessingUrl("/authenticate")  // 認証処理URL
            .usernameParameter("email")           // ユーザー名パラメータ
            .passwordParameter("passwd")          // パスワードパラメータ
            .defaultSuccessUrl("/dashboard")      // 成功時のリダイレクト先
            .failureUrl("/custom-login?error")    // 失敗時のリダイレクト先
            .permitAll()
        );
    
    return http.build();
}

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
    end

HTTP Basic認証の特徴は以下のとおりです。

特徴 説明
ステートレス セッションを使用しない
シンプル リクエストごとに認証情報を送信
毎回認証 各リクエストでAuthenticationManagerを呼び出し
HTTPS必須 認証情報がBase64エンコードのみのため
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// BasicAuthenticationFilterの設定例
@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/**")
        .httpBasic(basic -> basic
            .realmName("API Realm")
            .authenticationEntryPoint(customEntryPoint())
        )
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );
    
    return http.build();
}

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保護の設定オプションは以下のとおりです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf
            // 特定のパスをCSRF保護から除外
            .ignoringRequestMatchers("/api/webhook/**")
            // カスタムCSRFトークンリポジトリ
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            // カスタムRequestMatcher
            .requireCsrfProtectionMatcher(customMatcher())
        );
    
    return http.build();
}

ExceptionTranslationFilter

ExceptionTranslationFilterは、AuthenticationExceptionAccessDeniedExceptionを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の擬似コードは以下のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
try {
    filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
    if (!authenticated || ex instanceof AuthenticationException) {
        // 認証開始(ログインページへリダイレクトなど)
        authenticationEntryPoint.commence(request, response, ex);
    } else {
        // アクセス拒否処理
        accessDeniedHandler.handle(request, response, ex);
    }
}

AuthorizationFilter

AuthorizationFilterは、認可(アクセス制御)を担当する最後のセキュリティフィルターです。

flowchart TB
    A[AuthorizationFilter] --> B[AuthorizationManager]
    B --> C{認可チェック}
    C -->|許可| D[処理継続]
    C -->|拒否| E[AccessDeniedException]
    E --> F[ExceptionTranslationFilterで処理]

AuthorizationFilterは、設定された認可ルールに基づいてアクセス可否を判定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize
            // パスベースの認可
            .requestMatchers("/public/**").permitAll()
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .requestMatchers("/api/**").hasAuthority("API_ACCESS")
            // メソッドベースの認可
            .requestMatchers(HttpMethod.DELETE, "/users/**").hasRole("ADMIN")
            // IPアドレスベースの認可
            .requestMatchers("/internal/**").hasIpAddress("192.168.1.0/24")
            // その他はすべて認証必須
            .anyRequest().authenticated()
        );
    
    return http.build();
}

カスタムFilterの追加方法

Spring Securityでは、独自のFilterをSecurityFilterChainに追加できます。

Filterの追加位置

HttpSecurityは、Filterを追加するための3つのメソッドを提供します。

メソッド 説明
addFilterBefore(Filter, Class) 指定したFilterの前に追加
addFilterAfter(Filter, Class) 指定したFilterの後に追加
addFilterAt(Filter, Class) 指定したFilterの位置に追加(置換)

カスタムFilterの実装例

以下は、テナントIDを検証するカスタムFilterの例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;

public class TenantFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        
        String tenantId = request.getHeader("X-Tenant-Id");
        
        if (tenantId == null || tenantId.isEmpty()) {
            throw new AccessDeniedException("Tenant ID is required");
        }
        
        if (!isValidTenant(tenantId)) {
            throw new AccessDeniedException("Invalid Tenant ID");
        }
        
        // テナントコンテキストを設定
        TenantContext.setCurrentTenant(tenantId);
        
        try {
            filterChain.doFilter(request, response);
        } finally {
            // クリーンアップ
            TenantContext.clear();
        }
    }
    
    private boolean isValidTenant(String tenantId) {
        // テナントID検証ロジック
        return tenantId.matches("^[a-zA-Z0-9-]+$");
    }
}

SecurityFilterChainへの追加

カスタムFilterを適切な位置に追加します。認証済みユーザーの情報が必要な場合は、認証Filterの後に配置します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            // AnonymousAuthenticationFilterの後にTenantFilterを追加
            .addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class);
        
        return http.build();
    }
}

FilterをSpring Beanとして登録する場合の注意点

FilterをSpring Bean(@Componentなど)として宣言すると、Spring BootがServletコンテナにも自動登録してしまい、Filterが2回実行される問題が発生します。

この問題を回避するには、FilterRegistrationBeanを使用して自動登録を無効化します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Configuration
public class FilterConfig {

    @Bean
    public TenantFilter tenantFilter() {
        return new TenantFilter();
    }
    
    @Bean
    public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(
            TenantFilter tenantFilter) {
        FilterRegistrationBean<TenantFilter> registration = 
            new FilterRegistrationBean<>(tenantFilter);
        registration.setEnabled(false);  // 自動登録を無効化
        return registration;
    }
}

複数SecurityFilterChainの設定

APIとWebアプリケーションで異なるセキュリティ設定が必要な場合、複数のSecurityFilterChainを定義できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Configuration
@EnableWebSecurity
public class MultiSecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**")
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());
        
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/", "/home", "/css/**", "/js/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/")
                .permitAll()
            );
        
        return http.build();
    }
}

@Orderアノテーションで優先順位を指定し、数値が小さいほど先にマッチングが試行されます。

デバッグとトラブルシューティング

Spring Securityの動作を詳細に確認する方法を解説します。

ログレベルの設定

application.propertiesに以下を追加すると、FilterChainの実行順序や認証・認可の判定結果が出力されます。

1
2
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.security.web.FilterChainProxy=TRACE

出力例は以下のとおりです。

1
2
3
4
5
6
7
8
9
TRACE FilterChainProxy : Trying to match request against DefaultSecurityFilterChain 
DEBUG FilterChainProxy : Securing POST /api/users
TRACE FilterChainProxy : Invoking DisableEncodeUrlFilter (1/15)
TRACE FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/15)
TRACE FilterChainProxy : Invoking SecurityContextHolderFilter (3/15)
TRACE FilterChainProxy : Invoking HeaderWriterFilter (4/15)
TRACE FilterChainProxy : Invoking CsrfFilter (5/15)
...
DEBUG AuthorizationFilter : Authorized request

登録されたFilterの一覧を確認

アプリケーション起動時のDEBUGログで、登録されたFilterの一覧を確認できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
DEBUG DefaultSecurityFilterChain : Will secure any request with [
  DisableEncodeUrlFilter,
  WebAsyncManagerIntegrationFilter,
  SecurityContextHolderFilter,
  HeaderWriterFilter,
  CsrfFilter,
  LogoutFilter,
  UsernamePasswordAuthenticationFilter,
  DefaultLoginPageGeneratingFilter,
  DefaultLogoutPageGeneratingFilter,
  BasicAuthenticationFilter,
  RequestCacheAwareFilter,
  SecurityContextHolderAwareRequestFilter,
  AnonymousAuthenticationFilter,
  ExceptionTranslationFilter,
  AuthorizationFilter
]

一般的な問題と解決方法

以下は、よく遭遇する問題とその解決方法です。

問題 原因 解決方法
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にはSecurityContextHolderFilterUsernamePasswordAuthenticationFilterBasicAuthenticationFilterExceptionTranslationFilterAuthorizationFilterがある
  • カスタムFilterはaddFilterBefore/addFilterAfter/addFilterAtで適切な位置に追加する
  • 複数のSecurityFilterChain@Orderで優先順位を指定して定義できる

これらの内部構造を理解することで、Spring Securityのカスタマイズやデバッグが効率的に行えるようになります。

参考リンク