環境
- Spring Boot 3.2
- Spring Security 6.2
忙しい人のための結論
failureUrl()
を使いましょう。
レスポンスが返ってこない
👇のように記述すると、ログイン画面で間違ったユーザー名・パスワードを入力した場合にレスポンスが返ってこなくなりました。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(login -> login
.loginPage("/login")
.defaultSuccessUrl("/")
.failureForwardUrl("/login?error") // ここが問題?
.permitAll()
)...
👇のようにfailureUrl()
を使うと、ログイン画面で間違ったユーザー名・パスワードを入力した場合に/login?error
にリダイレクトされました。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(login -> login
.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?error") // これで正しく動いた
.permitAll()
)...
failureForwardUrl()
のソースを追う
failureForwardUrl()のソースコードを見てみると、AuthenticationFailureHandler
実装としてForwardAuthenticationFailureHandler
クラスを使っていることが分かります。
public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl) {
failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl));
return this;
}
AuthenticationFailureHandler
実装は名前の通り、認証が失敗した際に呼ばれる処理です。具体的にはonAuthenticationFailure()
メソッドが呼ばれます。
では、ForwardAuthenticationFailureHandler
クラスのonAuthenticationFailure()
メソッドを見てみましょう。
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
request.getRequestDispatcher(this.forwardUrl).forward(request, response);
}
指定されたURLにフォワードしているだけのようですね。
このメソッドにブレークポイントを設定→IDEでデバッグ起動→ブラウザからログイン画面に間違ったユーザー名・パスワードを入力→ステップ実行すると、このメソッドが何回も何回も実行されていることが分かります。
POSTリクエストをフォワードすると、指定したURL(今回は/login?error
)に同じリクエストパラメータをPOSTします。
加えて、SecurityConfig
クラスで.loginProcessingUrl()
を指定していないため、ユーザー名・パスワードのPOST先URLはデフォルトの/login
です。
以上のことから、次のような現象が起こっていたと考えられます。
- ブラウザからログイン画面に間違ったユーザー名・パスワードを入力→
/login
にPOSTする - ユーザー名・パスワードが間違っているので、認証が失敗し
ForwardAuthenticationFailureHandler
のonAuthenticationFailure()
が実行される - このメソッド内で
/login?error
にフォワードする。この際、間違ったユーザー名・パスワードもPOSTされる - 2と3が無限に繰り返される
failureUrl()
のソースを追う
では次にfailureUrl()
のソースコードを見てみましょう。AuthenticationFailureHandler
実装としてSimpleUrlAuthenticationFailureHandler
クラスを使っていることが分かります。
public final T failureUrl(String authenticationFailureUrl) {
T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl));
this.failureUrl = authenticationFailureUrl;
return result;
}
SimpleUrlAuthenticationFailureHandler
クラスのonAuthenticationFailure()
メソッドはこのようになっています。
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// 中略
if (this.forwardToDestination) {
this.logger.debug("Forwarding to " + this.defaultFailureUrl);
request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
}
else {
this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
}
}
forwardToDestination
フィールドの値がtrue
だったらフォワード、false
だったらリダイレクトするようです。このメソッドにブレークポイントを設定→IDEでデバッグ起動したところ、forwardToDestination
フィールドはfalse
になっていました。つまり、/login?error
にリダイレクトしていました。
フォワードではなくリダイレクトなので、間違ったユーザー名・パスワードが/login
にPOSTされ続けることはありません。
結論
ユーザー名・パスワードをPOSTするURLとfailureForwardUrl()
で指定したURLが同じ場合、認証失敗時にフォワードの無限ループになってしまいます。
failureUrl()
は指定されたURLにリダイレクトするので、フォワードの無限ループは起こりません。
なので、基本的にはfailureUrl()
を使えば間違いは無いと思います。