【Spring MVC + Spring Security】任意のログアウトハンドラを追加する方法

概要

ログアウト時に任意のログアウトハンドラを追加する方法についてまとめた。

 

前提

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

あわせて読みたい

概要 ログアウト機能を追加する方法についてまとめた。   前提 以下の記事の続きとなる。

【Spring MVC + Spring Security】ログアウト機能を追加する方法

 

ハンドラ実行タイミング

ログアウトするとき、ログアウトフィルターはログアウトハンドラのリスト(CompositeLogoutHandlerが保持)を順次実行する。
任意のログアウトハンドラ処理を実行したい場合、このリストに自作したハンドラを追加する。

 

【Spring MVC + Spring Security】任意のログアウトハンドラを追加する方法_ハンドラ実行タイミング
▲CompositeLogoutHandlerが保持するリストに、任意のログアウトハンドラを追加する

 

 

実装

ログアウトハンドラを自作して、ログアウト時に適用させる。

 

自作ログアウトハンドラの用意

ログアウトハンドラを自作する場合、LogoutHandlerを実装する。

 

LogoutSuccessHandler.java


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

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.logout.LogoutHandler;

public class LogoutSuccessHandler implements LogoutHandler {
    /** ロガー */
    private static final Logger logger = LoggerFactory.getLogger(LogoutSuccessHandler.class);
    
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // 任意の処理をここに追加
        logger.debug("★★ログアウト成功: LogoutHandler★★\n");
    }

}

 

public class LogoutSuccessHandler implements LogoutHandler {

LogoutHandlerを実装する。

 

@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

ログアウトメソッドをオーバーライドし、任意の処理を追加する。

 

CompositeLogoutHandlerの設定

ログアウト時に実行させたいログアウトハンドラをCompositeLogoutHandlerに設定する。

 

SecurityConfig.java


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

import java.util.Arrays;
import java.util.List;

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.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

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

@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 LogoutHandler compositeLogoutHandler() {
        List<LogoutHandler> handlers = Arrays.asList(
                new SecurityContextLogoutHandler(),
                new LogoutSuccessHandler()
            );

        return new CompositeLogoutHandler(handlers);
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        (略)
    }
}

 

List<LogoutHandler> handlers = Arrays.asList(
new SecurityContextLogoutHandler(),
new LogoutSuccessHandler()
);

順次実行させたいログアウトハンドラをリスト形式で定義する。
このリストに自作したログアウトハンドラを追加する。

SecurityContextLogoutHandlerは認証情報のクリアやセッションの無効化を行う。
通常はデフォルトでCompositeLogoutHandlerのリストに含まれるが、
CompositeLogoutHandlerを自ら定義する場合、このリストに明示的に追加する必要がある。

追加していない場合、
セキュリティコンテキストのクリア処理が実行されず、認証情報が残存する可能性がある。

 

return new CompositeLogoutHandler(handlers);

最終的に、定義したログアウトハンドラのリストをCompositeLogoutHandlerに渡してBean登録する。

このリストを利用することで、
複数の処理(セキュリティコンテキストのクリア、セッション破棄、監査ログ記録など)を柔軟に組み合わせることが可能となる。

 

LogoutFilterへの紐づけ

Bean登録したCompositeLogoutHandlerをLogoutFilterに設定する。

 

SecurityConfig.java


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

import java.util.Arrays;
import java.util.List;

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.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    (略)
    
    @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))

                // ログアウト機能定義
                .logout(logout -> logout
                        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                        .addLogoutHandler(compositeLogoutHandler())
                        .logoutSuccessUrl("/authentication?logout=true")
                );


        return http.build();
    }
}

 

.addLogoutHandler(compositeLogoutHandler())

Bean登録したCompositeLogoutHandlerをLogoutFilterに設定する。
これにより、ログアウト時に自作したログアウトハンドラが実行される。

 

まとめ

 

☑ 任意のログアウト処理を追加したい場合、LogoutHandlerを実装する

☑ 自作したログアウトハンドラはリスト化してCompositeLogoutHandlerに設定する

☑ LogoutFilterにこのCompositeLogoutHandlerを組み込むことで、ログアウト時に自作したハンドラが実行される

 

スポンサーリンク