埼玉在住エンジニアのナレッジ帳

webのエンジニアをやっており、日頃の開発で詰まったことについて残していきたいと思っています。https://ikujip.jpの開発も行っているため、そこで使った知識なども載せられればと思います。

Spring Securityで複数のログイン機能を作る時、鍵を握るのは@Order

ti-tomo-knowledge.hatenablog.com

までで基本的なログイン機能のご紹介まで行いましたが、
実際Webアプリを作り出すと、「ユーザ用のログイン画面」、「管理者用のログイン画面」といったように、
ログイン画面を複数用意しなければいけない場面があるのではないでしょうか。

そんな時はWebSecurityConfigurerAdapterを継承したSecurity Configの設定クラスを2つ用意し、
それぞれの中でHttpSecurityのconfigureを定義すれば問題ないのですが、そこで鍵を握るのは@Orderです。

例を見てみましょう。
※例では、WebSecurityConfigurerAdapterを継承したクラスとして、
UserSecurityConfigクラスとAdminSecurityConfigクラスを用意しています。
同一クラス内に複数のconfigureメソッドを使うと上手くいかないので...
また、formLogin()やlogout()などの記述は簡易化のために今回は省略します。

・UserSecurityConfigクラス

@Configuration
@EnableWebSecurity
@Order(2)
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/login").permitAll()
            .antMatchers("/**").hasRole("USER")
            .anyRequest().authenticated()
    }
}

・AdminSecurityConfigクラス

@Configuration
@EnableWebSecurity
@Order(1)
public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatcher("/admin/**")
            .authorizeRequests()
            .antMatchers("/admin/login").permitAll()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
    }
}

ここでそれぞれに@Orderのアノテーションが付いていますね。
こちらは認証処理の優先順位的なものを表しています。
WebSecurityConfigurerAdapterは常に一意の@Orderが必要で、
元々デフォルトで@Orderの数値が付与されているので(おそらく100)、
WebSecurityConfigurerAdapterの設置箇所が1箇所であればアノテーションは必要ありません。

しかし複数の設定をする場合、もしこのアノテーションがなければ、
両方のクラスにデフォルトの@Orderが付与され、
一意な優先順位ではなくなってしまうためにエラーが発生してしまいます。

@Order on WebSecurityConfigurers must be unique. Order of 100 was already used on ...

的な感じで。

これを回避するために@Orderの括弧内に一意の数値を入れますが、
ここで重要なのが順番です。

上記のソースの例で見ると、まず優先順位の高いAdminSecurityConfigクラスの「/admin/**」を見てから該当するURLであれば「/admin/login」へと処理が進み、
該当していなければ次に優先順位の高いUserSecurityConfigクラスの「/**」に該当しているかを見に行き、「/login」へと処理が進みます。
これが理想の動きで問題ありません。

ところがもし順番を逆にしてしまうと、
最初にUserSecurityConfigクラスの「/**」に該当しているかを見に行ってしまうので、
adminがついていようがいまいが、常に「/login」へと飛ばしてしまうため、
永遠にadmin画面に辿り着けないのです。

なので、複数のログイン機能を作る場合はお気をつけください。