概要
ログアウト時に任意のログアウトハンドラを追加する方法についてまとめた。
前提
以下の記事の続きとなる。
ハンドラ実行タイミング
ログアウトするとき、ログアウトフィルターはログアウトハンドラのリスト(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");
}
}
LogoutHandlerを実装する。
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 {
(略)
}
}
new SecurityContextLogoutHandler(),
new LogoutSuccessHandler()
);
順次実行させたいログアウトハンドラをリスト形式で定義する。
このリストに自作したログアウトハンドラを追加する。
SecurityContextLogoutHandlerは認証情報のクリアやセッションの無効化を行う。
通常はデフォルトでCompositeLogoutHandlerのリストに含まれるが、
CompositeLogoutHandlerを自ら定義する場合、このリストに明示的に追加する必要がある。
追加していない場合、
セキュリティコンテキストのクリア処理が実行されず、認証情報が残存する可能性がある。
最終的に、定義したログアウトハンドラのリストを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();
}
}
Bean登録したCompositeLogoutHandlerをLogoutFilterに設定する。
これにより、ログアウト時に自作したログアウトハンドラが実行される。
まとめ
☑ 任意のログアウト処理を追加したい場合、LogoutHandlerを実装する
☑ 自作したログアウトハンドラはリスト化してCompositeLogoutHandlerに設定する
☑ LogoutFilterにこのCompositeLogoutHandlerを組み込むことで、ログアウト時に自作したハンドラが実行される