95
Help us understand the problem. What are the problem?

posted at

updated at

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

この記事について

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

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

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

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

忙しい人のためのまとめ

  1. WebSecurityConfigurerAdapter を継承せずに SecurityFilterChain をBean定義してセキュリティ設定を書きましょう
  2. http.authorizeRequests() ではなく http.authorizeHttpRequests() を使いましょう
  3. web.ignoring() ではなく http.authorizeHttpRequests().mvcMatchers().permitAll() を使いましょう

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

環境

  • JDK 17
  • Spring Security 5.7.1
  • Spring Boot 2.7.0

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

重要な変更点たち

[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
                .mvcMatchers("/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 が使われます)。

5.4以前
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(authz -> authz
                .mvcMatchers("/css/**").permitAll()
                ...
5.5以降
@EnableWebSecurity
public class SecurityConfig {

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

[Spring Security 5.7] WebSecurityConfigurerAdapter が非推奨になった

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

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

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

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.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;

@EnableWebSecurity
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()
                .mvcMatchers("/").permitAll()
                .mvcMatchers("/general").hasRole("GENERAL")
                .mvcMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
        );
        return http.build();
    }

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

合わせて読みたい

Spring公式ブログにより詳細な解説があります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
95
Help us understand the problem. What are the problem?