Spring Security 5.4〜6.0でセキュリティ設定の書き方が大幅に変わる件
この記事について
最近(5.4〜6.0)のSpring Securityでは、セキュリティ設定の書き方が大幅に変わりました。その背景と、新しい書き方を紹介します。
非推奨になったものは、将来的には削除される可能性もあるため、なるべく早く新しい書き方に移行することをおすすめします。(既に削除されたものもあります)
この記事は、Spring Securityのアーキテクチャの理解(Filter Chain、 AuthenticationManager
、 AccessDecisionManager
など)を前提としています。あまり詳しくない方は、まずopengl_8080さんのブログを読むことをおすすめします。
サンプルコード -> https://github.com/MasatoshiTada/spring-security-intro
忙しい人のためのまとめ
-
@Configuration
を必ず付加しましょう -
WebSecurityConfigurerAdapter
を継承せずにSecurityFilterChain
をBean定義してセキュリティ設定を書きましょう -
http.authorizeRequests()
ではなくhttp.authorizeHttpRequests()
を使いましょう -
@EnableGlobalMethodSecurity
ではなく@EnableMethodSecurity
を使いましょう -
antMatchers()
やmvcMatchers()
ではなくrequestMatchers()
を使いましょう -
web.ignoring()
ではなくpermitAll()
を使いましょう -
.and()
は使わずにラムダ式で書きましょう
具体的なコード例はこちら
環境
- JDK 17
- Spring Security 6.0.1
- Spring Boot 3.0.2
将来のバージョン変更によって、この記事の内容が正しくなくなる可能性もあります。
重要な変更点たち
SecurityFilterChain
をBean定義できるようになった
[Spring Security 5.4] 対応するIssue → https://github.com/spring-projects/spring-security/issues/8804
SecurityFilterChain
自体はかなり以前からあったものです。これをBean定義できるようになったことにより、 従来のように WebSecurityConfigurerAdapter
を継承しなくても、設定が記述できるようになりました。
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(login -> login
...
}
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(login -> login
...
WebSecurityCustomizer
が導入された
[Spring Security 5.4] 対応するIssue -> https://github.com/spring-projects/spring-security/issues/8978
静的リソースなどをSpring Securityによる保護の対象外にしたい場合、従来は WebSecurityConfigurerAdapter
を継承して configure(WebSecurity)
をオーバーライドしていました。
同様の設定を、 WebSecurityCustomizer
をBean定義することで記述できるようになりました。
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers("/css/**");
}
@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
を使う機会もあまり無いかもしれません。
AuthorizationManager
が導入された
[Spring Security 5.5] 対応する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()
...
@EnableMethodSecurity
が導入された
[Spring Security 5.6] 対応するIssue -> https://github.com/spring-projects/spring-security/issues/9289
AOPによるメソッドの認可処理では、従来は @EnableGlobalMethodSecurity
が使われていました。これは AccessDesicionManager
を使ったものです。
そこで、 AuthorizationManager
を使う @EnableMethodSecurity
が導入されました。
WebSecurityConfigurerAdapter
が非推奨→削除になった
[Spring Security 5.7+6.0] 対応するIssue -> https://github.com/spring-projects/spring-security/issues/10822 ・ https://github.com/spring-projects/spring-security/issues/10902
ここまでで紹介した変更により WebSecurityConfigurerAdapter
が不要になったため、5.7で非推奨化、6.0で削除になりました。
antMatchers()
・ mvcMatchers()
が非推奨→削除になった
[Spring Security 5.8+6.0] 対応するIssue -> TBD
antMatchers()
・ mvcMatchers()
が5.8で非推奨化、6.0で削除されました。
代わりに requestMatchers()
を使います。
5.8と6.0は同時にリリースされています。控えめに言って鬼ですね・・・
@EnableWebSecurity
から @Configuration
が削除された
[Spring Security 6.0] 対応する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
.and()
が無効になる
[Spring Security 7.0] 対応するIssue -> TBD
Spring Security Referenceに、Spring Security 7.0への移行に関する説明が追加されました。
これによると、従来使われてきた .and()
を使った設定記述が無効(おそらくですが削除されるものと思われます)になるとのことです。
http.formLogin()
.loginProcessingUrl("/login")
.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
...
これはIDEなどでインデントを整えると次のようになってしまい、どこからどこまでが1つのかたまりなのかが分かりづらくなってしまいます。
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に置いてあります)。
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公式ブログにより詳細な解説があります。
追加
5.8以降から、ログイン後のURLにcontinue
クエリパラメータが付く場合があります。詳細は👇の記事を参照してください。
Discussion