【Spring MVC + Spring Security】認証成功/失敗時の処理をハンドラで制御する方法

概要

認証成功/失敗時に呼び出されるハンドラを利用して、後続処理を制御する方法についてまとめた。

 

前提

以下の記事の続きとなる。

あわせて読みたい

概要 Spring Securityの認証成功/失敗イベントをハンドリングする方法についてまとめた。   前提 以下の記事の続きとなる。

認証イベントをハンドリングする方法

 

ハンドラとは

特定の処理のこと。
リスナーとの違いは以下のようになる。

 

種類 役割 起動方法
ハンドラ 特定の処理を実行する責務を持つ 処理を行う側から明示的・直接的に呼び出される
リスナー イベントの発生を観測する責務を持つ イベントの発行元から通知を受け取って反応する

 

認証ハンドラ

認証の流れの中で、成功/失敗が確定した直後に呼び出される処理のことを認証ハンドラと呼ぶ。
Spring Securityは認証成功/失敗時のハンドラを提供している。

 

ハンドラ パッケージ 用途
AuthenticationSuccessHandler org.springframework.security.web.authentication 認証成功時の処理を定義する
例):セッション初期化、ログ出力など
AuthenticationFailureHandler org.springframework.security.web.authentication 認証失敗時の処理を定義する
例):エラーメッセージ設定、認証失敗回数記録、ログ出力など

 

 

実装

認証ハンドラを追加する。

 

認証成功ハンドラ

認証成功時の処理を定義する。

 

LoginSuccessHandler.java


package com.example.prototype.biz.security.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    /** ロガー */
    private static final Logger logger = LoggerFactory.getLogger(LoginSuccessHandler.class);
    
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        // 任意の処理をここに追加
        
        // ログ出力
        logger.debug("★★ログイン成功: AuthenticationSuccessHandlerハンドラ★★\n");
        
        response.sendRedirect(request.getContextPath() + "/");
    }

}

 

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

AuthenticationSuccessHandlerを実装してメソッドをオーバーライドする。
この処理内でサービスクラスなどをDIする想定がない場合、コンポートネント化は不要。

 

認証失敗ハンドラ

認証失敗時の処理を定義する。

 

LoginFailureHandler.java


package com.example.prototype.biz.security.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
    /** ロガー */
    private static final Logger logger = LoggerFactory.getLogger(LoginFailureHandler.class);
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        // 任意の処理をここに追加
        
        // ログ出力
        logger.debug("★★ログイン成失敗 AuthenticationFailureHandlerハンドラ★★\n");
        
        // 認証エラー情報をセッションに設定
        request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
        
        response.sendRedirect(request.getContextPath() + "/authentication?error");
    }

}

 

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

AuthenticationFailureHandlerを実装してメソッドをオーバーライドする。
この処理内でサービスクラスなどをDIする想定がない場合、コンポートネント化は不要。

 

// 認証エラー情報をセッションに設定
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);

画面にエラーメッセージを表示させるため、認証エラー情報をセッションに格納する。
※デフォルトではSimpleUrlAuthenticationFailureHandlerにてエラー情報がセッションに格納される

 

ハンドラの設定

作成した認証ハンドラをSecurityFilterChainに設定する。

 

SecurityConfig.java


package com.example.prototype.biz.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.example.prototype.biz.security.handler.LoginFailureHandler;
import com.example.prototype.biz.security.handler.LoginSuccessHandler;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;
    
    @Autowired
    private LoginFailureHandler loginFailureHandler;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        UserDetails disabledUser = User.withUsername("user")
            .password(encoder.encode("password"))
            .roles("USER")
            .build();
        
        return new InMemoryUserDetailsManager(disabledUser);
    }
    
    @Bean
    public DaoAuthenticationProvider authenticationProvider(MessageSource messageSource,
            PasswordEncoder passwordEncoder) {
        var provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService(passwordEncoder));
        provider.setPasswordEncoder(passwordEncoder);
        provider.setMessageSource(messageSource);
        return provider;
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // CSRF機能を無効
                .csrf(csrf -> csrf.disable())

                // 認可ポリシー定義
                .authorizeHttpRequests(auth -> auth
                        // 認証不要のURL
                        .requestMatchers(
                                new AntPathRequestMatcher("/authentication"),
                                new AntPathRequestMatcher("/logout")
                                ).permitAll()
                        .anyRequest().authenticated())

                // ログイン機能定義
                .formLogin(form -> form
                        .loginPage("/authentication")
                        .loginProcessingUrl("/authentication/process")
                        .usernameParameter("loginId")
                        .passwordParameter("pass")
                        .successHandler(loginSuccessHandler)
                        .failureHandler(loginFailureHandler)
                        );

        return http.build();
    }
}

 

.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)

認証成功時と失敗時のハンドラを設定する。

 

 

まとめ

 

☑ 認証ハンドラをカスタムすることで、認証成功/失敗時の挙動を制御できる

☑ 認証成功時を制御したい場合、AuthenticationSuccessHandlerを実装する

☑ 認証失敗時を制御したい場合、AuthenticationFailureHandlerを実装する

 

スポンサーリンク