😸

Spring Security 5.4〜6.0でセキュリティ設定の書き方が大幅に変わる件

2024/08/13に公開

この記事について

最近(5.4〜6.0)のSpring Securityでは、セキュリティ設定の書き方が大幅に変わりました。その背景と、新しい書き方を紹介します。

非推奨になったものは、将来的には削除される可能性もあるため、なるべく早く新しい書き方に移行することをおすすめします。(既に削除されたものもあります)

この記事は、Spring Securityのアーキテクチャの理解(Filter Chain、 AuthenticationManagerAccessDecisionManager など)を前提としています。あまり詳しくない方は、まずopengl_8080さんのブログを読むことをおすすめします。

サンプルコード -> https://github.com/MasatoshiTada/spring-security-intro

忙しい人のためのまとめ

  1. @Configuration を必ず付加しましょう
  2. WebSecurityConfigurerAdapter を継承せずに SecurityFilterChain をBean定義してセキュリティ設定を書きましょう
  3. http.authorizeRequests() ではなく http.authorizeHttpRequests() を使いましょう
  4. @EnableGlobalMethodSecurity ではなく @EnableMethodSecurity を使いましょう
  5. antMatchers()mvcMatchers() ではなく requestMatchers() を使いましょう
  6. web.ignoring() ではなく permitAll() を使いましょう
  7. .and() は使わずにラムダ式で書きましょう

具体的なコード例はこちら

環境

  • JDK 17
  • Spring Security 6.0.1
  • Spring Boot 3.0.2

将来のバージョン変更によって、この記事の内容が正しくなくなる可能性もあります。

重要な変更点たち

[Spring Security 5.4] SecurityFilterChain をBean定義できるようになった

対応するIssue → https://github.com/spring-projects/spring-security/issues/8804

SecurityFilterChain 自体はかなり以前からあったものです。これをBean定義できるようになったことにより、 従来のように WebSecurityConfigurerAdapter を継承しなくても、設定が記述できるようになりました。

5.3以前
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin(login -> login
                ...
    }
5.4以降
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.formLogin(login -> login
                ...

[Spring Security 5.4] WebSecurityCustomizer が導入された

対応するIssue -> https://github.com/spring-projects/spring-security/issues/8978

静的リソースなどをSpring Securityによる保護の対象外にしたい場合、従来は WebSecurityConfigurerAdapter を継承して configure(WebSecurity) をオーバーライドしていました。

同様の設定を、 WebSecurityCustomizer をBean定義することで記述できるようになりました。

5.3以前
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/css/**");
    }
5.4以降(ただし推奨されない)
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring().mvcMatchers("/css/**");
    }

ただし上記の設定は、指定したURLに対してはSpring Securityが完全に何もしなくなるため、安全でなくなる可能性があります。実際、この設定を記述していると起動時に次のようなWARNログが出力されます。

...
20xx-xx-xx xx:xx:xx.xxx  WARN 3029 --- [           main] o.s.s.c.a.web.builders.WebSecurity       : You are asking Spring Security to ignore Mvc [pattern='/css/**']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
...

同様の設定については、 permitAll() を使った書き方が推奨されています(Javadoc参照)。

推奨される書き方
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authz -> authz
                .requestMatchers("/css/**").permitAll()
                // Spring Boot利用時は下記でも同様
//                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                ...

セキュリティ無視の設定以外で WebSecurity を使うことはあまり無いと思いますので、 WebSecurityCustomizer を使う機会もあまり無いかもしれません。

[Spring Security 5.5] AuthorizationManager が導入された

対応するIssue -> https://github.com/spring-projects/spring-security/issues/8900

従来、Spring Securityの認可処理は AccessDecisionManager が行っています。これを置き換えるクラスが AuthorizationManager です(👆のIssueによれば、 AccessDecisionManager は将来的には非推奨になるようです)。

また、従来は FilterSecurityInterceptor というフィルターが AccessDecisionManager を使って認可処理をしています。このフィルターを置き換えるべく、 AuthorizationManager を使った AuthorizationFilter も導入されました。

AuthorizationFilter を使うには、 http.authorizeHttpRequests() を利用して設定を記述します( http.authorizeRequests() を利用した場合、従来どおり FilterSecurityInterceptor が使われます)。

以前の書き方
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(authz -> authz
                .mvcMatchers("/css/**").permitAll()
                ...
現在の書き方
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authz -> authz
                .requestMatchers("/css/**").permitAll()
                ...

[Spring Security 5.6] @EnableMethodSecurity が導入された

対応するIssue -> https://github.com/spring-projects/spring-security/issues/9289

AOPによるメソッドの認可処理では、従来は @EnableGlobalMethodSecurity が使われていました。これは AccessDesicionManager  を使ったものです。

そこで、 AuthorizationManager を使う @EnableMethodSecurity が導入されました。

[Spring Security 5.7+6.0] WebSecurityConfigurerAdapter が非推奨→削除になった

対応するIssue -> https://github.com/spring-projects/spring-security/issues/10822https://github.com/spring-projects/spring-security/issues/10902

ここまでで紹介した変更により WebSecurityConfigurerAdapter が不要になったため、5.7で非推奨化、6.0で削除になりました。

[Spring Security 5.8+6.0] antMatchers()mvcMatchers() が非推奨→削除になった

対応するIssue -> TBD

antMatchers()mvcMatchers() が5.8で非推奨化、6.0で削除されました。

代わりに requestMatchers() を使います。

5.8と6.0は同時にリリースされています。控えめに言って鬼ですね・・・

[Spring Security 6.0] @EnableWebSecurity から @Configuration が削除された

対応するIssue -> https://github.com/spring-projects/spring-security/issues/6613

Spring Security 5.8までは @EnableWebSecurity@Configuration が付加されていたため、 @EnableWebSecurity を付加するだけでJava Configクラスと認識されていました。

しかし6.0で @EnableWebSecurity から @Configuration が削除されたため、Java Configクラスには明示的に @Configuration を付加する必要があります。

非推奨とか削除じゃないので気づきづらい、一番イヤな変更点ですw

[Spring Security 7.0] .and()が無効になる

対応するIssue -> TBD

Spring Security Referenceに、Spring Security 7.0への移行に関する説明が追加されました。

これによると、従来使われてきた .and() を使った設定記述が無効(おそらくですが削除されるものと思われます)になるとのことです。

and()を利用した書き方
        http.formLogin()
                .loginProcessingUrl("/login")
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .failureUrl("/login?error")
                .permitAll()
                .and()
        .logout()
                .logoutSuccessUrl("/")
                .and()
        ...

これはIDEなどでインデントを整えると次のようになってしまい、どこからどこまでが1つのかたまりなのかが分かりづらくなってしまいます。

and()を利用した書き方をIDEでフォーマットした結果
        http.formLogin()
            .loginProcessingUrl("/login")
            .loginPage("/login")
            .defaultSuccessUrl("/")
            .failureUrl("/login?error")
            .permitAll()
            .and()
            .logout()
            .logoutSuccessUrl("/")
            .and()
            ...

Spring Security 5.2で導入されたラムダ式による記述を使うと、その心配はありません。

ラムダ式を利用した書き方
        http.formLogin(login -> login
                .loginProcessingUrl("/login")
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .failureUrl("/login?error")
                .permitAll()
        ).logout(logout -> logout
                .logoutSuccessUrl("/")
        )...

Spring Security 7.0のリリース日は、2023年5月現在、まだ決まっていません。しかし、徐々にラムダ式に書き換えていったほうがいいでしょう。

なぜこれらの変更が行われたのか?

Issue(これとかこれ)を読む限りでは、WebFlux版のSpring Securityと書き方を揃えたいという意向が読み取れます。

もう1点は完全に僕の想像ですが、既存のあまり良くないAPIを変えたかったのではないでしょうか。例えば WebSecurityConfigurerAdapter には、引数の型が違う configure() メソッドが3つもあります。また AccessDecisionManager による認可は、 AccessDecisionManager から更に複数の AccessDecisionVoter に委譲してそれらの結果をまとめて判断する、という複雑な処理になっています。

新しい書き方のコード例

ここまでをまとめて、実際のコード例はこのようになります(全体像はGitHubに置いてあります)。

SecurityConfig.java
package com.example.springsecurityintro;

import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.formLogin(login -> login
                .loginProcessingUrl("/login")
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .failureUrl("/login?error")
                .permitAll()
        ).logout(logout -> logout
                .logoutSuccessUrl("/")
        ).authorizeHttpRequests(authz -> authz
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                .requestMatchers("/").permitAll()
                .requestMatchers("/general").hasRole("GENERAL")
                .requestMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
        );
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

合わせて読みたい

5.7までの変更点については、Spring公式ブログにより詳細な解説があります。

https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

追加

5.8以降から、ログイン後のURLにcontinueクエリパラメータが付く場合があります。詳細は👇の記事を参照してください。

https://qiita.com/suke_masa/items/d49e8599b167550e92c4

Discussion