|

Spring Security 7移行ガイド:SecurityFilterChainはどう変わる?

はじめに

Spring Boot / Spring Security を使っているプロジェクトでは、バージョンアップのたびに「セキュリティ設定の書き方が変わっていてビルドが通らない」「古い記事のコードをそのまま使えない」といった問題に遭遇しがちです。

特に Spring Security 7 への移行では、SecurityFilterChain そのものの考え方が大きく変わるというより、SecurityFilterChain を定義するときの書き方がより明確に整理される、という理解が近いです。

Spring Security 7 では、従来のチェーン形式の設定ではなく Lambda DSL を使った設定が前提になります。公式ドキュメントでも、Spring Security 7 では Lambda DSL の利用が必須になると説明されています。

この記事では、Spring Security 6.x から 7 へ移行する人に向けて、SecurityFilterChain の書き方がどう変わるのか、既存コードをどう直せばよいのかを実装例付きで解説します。

SecurityFilterChainはなくならない

最初に安心してほしいのですが、Spring Security 7 でも SecurityFilterChain は引き続き使います。

変わるのは主に以下です。

  • Lambda DSL が必須になる
  • .and() でつなぐ古い書き方をやめる
  • authorizeRequests ではなく authorizeHttpRequests を使う
  • apply() ではなく with() を使う
  • リクエストマッチャー周りの扱いが変わる
  • AntPathRequestMatcher / MvcRequestMatcher 前提のコードを見直す

SecurityFilterChain は、Spring Security が「このリクエストにはどのセキュリティフィルター群を適用するか」を決めるための設定です。公式ドキュメントでも、SecurityFilterChainFilterChainProxy によって現在のリクエストに適用する Security Filter を決定するために使われる、と説明されています。

つまり、Spring Security 7 でも基本構造は次のままです。

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        // ここにセキュリティ設定を書く
        ;

    return http.build();
}

ただし、中身の書き方を Spring Security 7 向けに整える必要があります。

移行のおすすめ順序

いきなり Spring Security 7 に上げるより、まずは Spring Security 6.5 系で警告や非推奨設定を潰してから 7 に上げるのがおすすめです。

公式の移行ガイドでも、Spring Security 6.5 は 6.x 世代の最後のリリースであり、7.0 方式に備えるための準備手順が用意されているため、まず 6.5 と準備手順を使うことが推奨されています。

おすすめの流れは以下です。

  1. Spring Boot / Spring Security 6.5系へ上げる
  2. 非推奨APIや警告を解消する
  3. SecurityFilterChainをLambda DSLへ寄せる
  4. requestMatchersやCustom DSLを見直す
  5. テストで認可・認証の挙動を確認する
  6. Spring Security 7へ上げる

移行作業で一番つらいのは、バージョンアップ後に大量のコンパイルエラーと認可エラーが同時に出ることです。
まず 6.5 系で「7 に近い書き方」に直しておくと、かなり安全に移行できます。

変更点1:Lambda DSLが必須になる

Spring Security 7 移行で最も重要なのが Lambda DSL です。

古い書き方

Spring Security 5.x や 6.x 初期の記事では、以下のような .and() でつなぐ書き方をよく見かけます。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests()
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .logoutUrl("/logout");

        return http.build();
    }
}

Spring Security 7 では、このようなチェーン形式ではなく Lambda DSL に寄せます。

Spring Security 7向けの書き方

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
            );

        return http.build();
    }
}

ポイントは .and() を使わないことです。

Lambda DSL では、それぞれの設定ブロックがラムダ式の中で完結します。
authorizeHttpRequestsformLoginlogout などがそれぞれ独立して見えるので、設定の見通しがよくなります。

変更点2:Customizer.withDefaults()を使う場面が増える

デフォルト設定で有効化したい場合は、Customizer.withDefaults() を使います。

例えば HTTP Basic 認証をデフォルト設定で有効化する場合は以下です。

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());

        return http.build();
    }
}

Customizer.withDefaults() は、「Spring Security が用意している標準設定で有効化する」という意味です。
公式ドキュメントでも、Customizer.withDefaults() はデフォルト設定を使ってセキュリティ機能を有効化するショートカットとして説明されています。

変更点3:authorizeRequestsではなくauthorizeHttpRequestsへ寄せる

古いプロジェクトでは、以下のような設定が残っていることがあります。

http
    .authorizeRequests()
        .antMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated();

Spring Security 6 以降では、基本的に authorizeHttpRequests を使う形へ寄せます。

http
    .authorizeHttpRequests(authorize -> authorize
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
    );

公式ドキュメントでも、HttpSecurity を使う場合は少なくとも authorizeHttpRequests を設定する例が示されています。

変更点4:antMatchers / mvcMatchers前提のコードを見直す

Spring Security 7 では、リクエストマッチャー周りも見直しポイントです。

特に以下のような古い書き方が残っている場合は注意です。

http
    .authorizeRequests()
        .antMatchers("/api/public/**").permitAll()
        .mvcMatchers("/admin/**").hasRole("ADMIN");

Spring Security 7 では、AntPathRequestMatcherMvcRequestMatcher はサポートされなくなり、Java DSL では URI を絶対パス、ただし context path を除いた形式で指定する必要があるとされています。また、Spring Security 7 では PathPatternRequestMatcher がデフォルトで使われる方針です。

基本的には、以下のように requestMatchers を使う形に寄せます。

http
    .authorizeHttpRequests(authorize -> authorize
        .requestMatchers("/api/public/**").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
    );

ここで重要なのは、/api/public/** のように 先頭のスラッシュ付きのパスで書くことです。

変更点5:複数のSecurityFilterChainは「順序」が重要

API と画面でセキュリティ設定を分けたい場合、複数の SecurityFilterChain を定義することがあります。

例えば、以下のようなケースです。

  • /api/** : JWT認証
  • その他 : フォームログイン

この場合は @OrdersecurityMatcher を使って、どのリクエストにどの SecurityFilterChain を適用するかを明確にします。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    @Order(1)
    SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**")
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(Customizer.withDefaults())
            )
            .csrf(csrf -> csrf.disable());

        return http.build();
    }

    @Bean
    @Order(2)
    SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/", "/login", "/css/**", "/js/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            );

        return http.build();
    }
}

FilterChainProxy は複数の SecurityFilterChain がある場合、最初にマッチしたチェーンだけを適用します。公式ドキュメントでも、/api/** にマッチした場合は、そのチェーンだけが呼び出され、他のチェーンは呼び出されないと説明されています。

そのため、より具体的な /api/** のようなチェーンを先に定義し、汎用的なチェーンを後ろに置くのが基本です。

変更点6:Custom DSLはapply()ではなくwith()へ

独自のセキュリティ DSL を使っているプロジェクトでは、以下のようなコードがあるかもしれません。

http
    .apply(new MyCustomDsl())
    .and()
    .authorizeHttpRequests(authorize -> authorize
        .anyRequest().authenticated()
    );

Spring Security 7 では .and() を前提にした設定が使えなくなるため、apply() ではなく with() を使う形へ移行します。公式ドキュメントでも、Spring Security 6.2 から apply() は非推奨となり、7.0 では削除されるため、代わりに .with() を使うことが推奨されています。

http
    .with(new MyCustomDsl(), customizer -> {
        customizer.enabled(true);
    })
    .authorizeHttpRequests(authorize -> authorize
        .anyRequest().authenticated()
    );

独自 DSL を使っていないプロジェクトでは、この対応は不要です。

変更点7:dispatcherTypeMatchersを使う場面がある

エラーページや forward されたリクエストをどう扱うかを制御している場合、shouldFilterAllDispatcherTypes(false) のような古い設定が残っていることがあります。

Spring Security 7 へ向けては、dispatcherTypeMatchers を使う形に寄せます。

import jakarta.servlet.DispatcherType;

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize
            .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
            .requestMatchers("/error").permitAll()
            .anyRequest().authenticated()
        );

    return http.build();
}

エラーページが 403 になってしまう、例外ハンドリング後の画面遷移がうまくいかない、といった場合はこのあたりを確認するとよいです。

APIサーバー向けの実装例

画面を持たない REST API / BFF では、以下のような設定がよく使われます。

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .cors(withDefaults())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(withDefaults())
            );

        return http.build();
    }
}

ポイントは以下です。

  • APIではCSRFを無効化することが多い
  • セッションレスならSessionCreationPolicy.STATELESSを指定する
  • 公開APIやhealth checkは明示的にpermitAllする
  • JWTを使う場合はoauth2ResourceServer().jwt()をLambda DSLで書く

ただし、CSRFを無効化してよいかはアプリの認証方式によります。
Cookieベースのログインやブラウザ画面がある場合は、単純に無効化せず、要件に応じて検討してください。

画面ありWebアプリ向けの実装例

フォームログインを使う Web アプリでは、以下のようになります。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/", "/login", "/css/**", "/js/**", "/images/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/", true)
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            );

        return http.build();
    }
}

画面ありアプリの場合は、以下を忘れがちです。

  • ログイン画面をpermitAllする
  • CSS / JS / 画像などの静的リソースをpermitAllする
  • 管理画面などの権限設定を明示する
  • ログアウト後の遷移先を決める

まとめ

Spring Security 7 への移行では、SecurityFilterChain がなくなるわけではありません。
むしろ、SecurityFilterChain を中心にした設定はそのまま使い続けます。

一方で、設定の書き方は Lambda DSL 前提になります。

特に重要なのは以下です。

  • SecurityFilterChain Bean は継続して使う
  • Lambda DSL に統一する
  • .and() を使わない
  • authorizeHttpRequests を使う
  • requestMatchers へ寄せる
  • 複数チェーンでは @Order と securityMatcher を明示する

移行作業では、一気に全部直そうとするとつらくなります。
まずは Spring Security 6.5 系で非推奨コードを減らし、設定を Lambda DSL に寄せてから 7 へ上げるのが安全です。

Spring Security の設定は少し読みにくく感じることもありますが、SecurityFilterChain の単位で「どのリクエストに、どの認証・認可ルールを適用するのか」を整理すると、移行作業はかなり進めやすくなります。

是非フォローしてください

最新の情報をお伝えします

類似投稿