くちたと計算記

プログラミングのことを書きます

Spring Securityで自作したAuthenticationFilterとAuthentiactionProviderを使う方法

概要

Spring Security 5.7.xではWebSecurityConfigurerAdapterを拡張する記法が非推奨となり、SecurityFilterChainをBeanとして登録する記法が推奨されています。

そこで自作したAuthenticationFilterAuthenticationProviderを使う設定ファイルを書き直そうと試みました。

@EnableWebSecurity
@Configuration
public class OldConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(AuthenticationProviderBuilder auth) {
    auth.authenticationProvider(new MyAuthenticationProvider());
  }

  @Override
  protected void configure(HttpSecuirty http) {
    MyAuthenticationFilter filter = new MyAuthenticationFilter();
    filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/my-login", "POST"));
    filter.setAuthenticationManager(super.authenticationManagerBean())
    http
        .formLogin(formLogin -> formLogin
            .loginPage("/my-login-form")
            .loginProcessingUrl("/my-login")
        )
        .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
  }
}

このときに悩んだこととを二つ紹介します。

1. 自作したAuthenticationProviderAuthenticationManagerに登録する方法が分からない

今までWebSecurityConfigurerAdapter#configure(AuthenticationProviderBuilder)メソッドで自作したMyAuthenticationProviderAuthentcationManagerに登録していました。 WebSecurityConfigurerAdapterクラスを継承しないことに伴い、書き直す必要があります。

UsernamePasswordAuthenticationFilterを使う場合は今まで通りHttpSecuirtyの設定をすれば解決します。

@Bean
public SecurityFilterChain securityFilterChain(HttpSecuirty http) {
  MyAuthenticationFilter filter = new MyAuthenticationFilter();
  filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/my-login", "POST"));
  filter.setAuthenticationManager(/* どこからかAuthenticationManagerを取得したい */)

  return http
    .formLogin(formLogin -> formLogin
        .loginPage("/my-login-form")
        .loginProcessingUrl("/my-login")
    )
    .authenticationProvider(new MyAuthenticationProvider())
    .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    .build();
}

この設定をすると、HttpSecurity#build()を呼び出したときに、AuthenticationManagerインスタンスが生成されます。 それではAuthenticationManagerを自作したAuthenticationFilterに設定できなくなってしまいます。 そこでSpring Securityの仕組みに頼らず、自分でAuthenticationManagerを生成しました。 AuthenticationManagerの実装には、ProviderManagerを使用します。

@Bean
public SecurityFilterChain securityFilterChain(HttpSecuirty http) {
  AuthenticationManager authenticationManager = new ProviderManager(new MyAuthenticationProvider());

  MyAuthenticationFilter filter = new MyAuthenticationFilter();
  filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/my-login", "POST"));
  filter.setAuthenticationManager(authenticationManager);

  return http
    .formLogin(formLogin -> formLogin
        .loginPage("/my-login-form")
        .loginProcessingUrl("/my-login")
    )
    .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    .build();
}

2. formLoginメソッドで設定した内容と自作したAuthenticationFilterにする設定はどちらが優先されるのか

修正前の設定では、filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/my-login", "POST"))と、formLogin.loginProcessingUrl("/my-login")の2か所で認証処理を呼び出すパスを設定しています。 どちらかの設定が無駄になっていると思ったので、FormLoginConfigurerのドキュメントを確認しました。

Adds form based authentication. All attributes have reasonable defaults making all parameters are optional. If no loginPage(String) is specified, a default login page will be generated by the framework. Security Filters

The following Filters are populated

UsernamePasswordAuthenticationFilter

Shared Objects Created

The following shared objects are populated

AuthenticationEntryPoint

docs.spring.io

FormLoginConfigurerUsernamePasswordAuthenticationFilterを構築するための設定で、副次的にAuthenticationEntryPointオブジェクトを生成しているとあります。 FormLoginConfigurerでパスを設定しても自作したAuthenticationFilterには設定されませんので、formLoginを使わないことにしました。

@Bean
public SecurityFilterChain securityFilterChain(HttpSecuirty http) {
  AuthenticationManager authenticationManager = new ProviderManager(new MyAuthenticationProvider());

  MyAuthenticationFilter filter = new MyAuthenticationFilter();
  filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/my-login", "POST"));
  filter.setAuthenticationManager(authenticationManager)

  return http
    .exceptionHandling(exceptionHandling -> exceptionHandling
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/my-login-form"))
    )
    .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    .build();
}

最後に

フレームワークの設定はかなり小手先の領域にはなりますが、それでも自分が何をやりたくて、フレームワークのどの部分を拡張してくと良いのかを考えるのはとても面白かったです。 そのためにも、使用するフレームワークやライブラリのリファレンスに載っているような内容は理解しておきたいです。