HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🤩
개발
/
🔐
Spring Security
/
Spring Security 인증 이벤트

Spring Security 인증 이벤트

AuthenticationEventPublisher (이벤트 생성 주체)
  • 인증 성공 또는 실패가 발생했을 때 이벤트를 전달하기 위한 이벤트 퍼블리셔 인터페이스
  • 기본 구현체로 DefaultAuthenticationEventPublisher 클래스가 사용됨
    • ProviderManager에서 호출이 되게 됨
public interface AuthenticationEventPublisher { void publishAuthenticationSuccess(Authentication authentication); void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication); }
AuthenticationEventPublisher 구현 발췌
이벤트의 종류
  • AuthenticationSuccessEvent — 로그인 성공 이벤트
  • AbstractAuthenticationFailureEvent — 로그인 실패 이벤트 (실패 이유에 따라 다양한 구체 클래스가 정의되 있음)
이벤트에 대해서 반응하도록 하는 방법1. successfulAuthentication 메소드를 override (혹은 AuthenticationSuccessHandler 재정의)2. 이벤트 리스너 추가비동기로 이벤트 리스너 등록방법(@Async, @EnableAsync)

이벤트에 대해서 반응하도록 하는 방법

인증 성공 또는 실패가 발생했을 때 관련 이벤트(ApplicationEvent)가 발생하고, 해당 이벤트에 관심있는 컴포넌트는 이벤트를 구독할 수 있다.
⚠️
주의해야 할 부분은 Spring의 이벤트 모델이 동기적이라는 것이다. 따라서 이벤트를 구독하는 리스너의 처리 지연은 이벤트를 발생시킨 요청의 응답 지연에 직접적인 영향을 미친다.
그렇다면 왜 이벤트 모델을 사용해야 할까? 이벤트 모델은 컴포넌트 간의 느슨한 결합을 유지하는데 도움을 준다. 예를들어 로그인 성공 시 사용자에게 이메일을 발송해야 하는 시스템을 생각해보자. 우리는 이제 Spring Security의 인프라스트럭처를 잘 이해하고 있으므로 최대한 이를 이용하려 할 것이다.
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authResult); SecurityContextHolder.setContext(context); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult)); } this.rememberMeServices.loginSuccess(request, response, authResult); if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } this.successHandler.onAuthenticationSuccess(request, response, authResult); }
AbstractAuthenticationProcessingFilter의 successfulAuthentication 원본 메서드

1. successfulAuthentication 메소드를 override (혹은 AuthenticationSuccessHandler 재정의)

  • AbstractAuthenticationProcessingFilter 추상 클래스를 상속하고, 인증이 성공했을 때 수행되는 successfulAuthentication 메소드를 override 함
    • 또는 AuthenticationSuccessHandler를 재정의할 수 있음
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { sendEmail(authResult); super.successfulAuthentication(request, response, chain, authResult); }
그런데 어느날, 로그인 성공 시 이메일 뿐만 아니라 SMS 전송도 함께 이루어져야 한다는 요구사항을 받았다. 우리는 앞서 만들었던 successfulAuthentication 메소드를 수정하기로 한다.
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { sendEmail(authResult); sendSms(authResult); super.successfulAuthentication(request, response, chain, authResult); }
이 처럼 요구사항이 변화할 때 관련 코드를 지속해서 수정해야하는 것은 해당 코드가 높은 결합도를 가지고 있고, 확장에 닫혀 있기 때문이다. 이 문제를 이벤트 발생-구독 모델로 접근한다면 Spring Security의 인프라스트럭처 위에서 수정해야 하는 것은 아무것도 없다. 단지 인증 성공 이벤트를 구독하는 리스너를 추가만 하면된다.

2. 이벤트 리스너 추가

  • 이메일 발송 리스너 — 로그인 성공 이벤트를 수신하고, 이메일을 발송함
  • SMS 발송 리스너 — 로그인 성공 이벤트를 수신하고, SMS를 발송함
또 다른 발송 채널을 추가해야 한다면 기존 코드는 수정할 필요가 없다. 그저 필요한 리스너를 추가하면된다.
이벤트 리스너 등록 방법
  • @EventListener 어노테이션을 이용하여 리스너 등록
@Component public class CustomAuthenticationEventHandler { private final Logger log = LoggerFactory.getLogger(getClass()); @EventListener public void handleAuthenticationSuccessEvent(AuthenticationSuccessEvent event) { Authentication authentication = event.getAuthentication(); log.info("Successful authentication result: {}", authentication.getPrincipal()); } @EventListener public void handleAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) { Exception e = event.getException(); Authentication authentication = event.getAuthentication(); log.warn("Unsuccessful authentication result: {}", authentication, e); } }
  • 주의해야 할 점은 Spring의 이벤트 모델이 동기적이기 때문에 이벤트를 구독하는 리스너에서 처리가 지연되면, 이벤트를 발행하는 부분 처리도 지연됨
    • @EnableAsync로 비동기 처리를 활성화하고, @Async 어노테이션을 사용해 이벤트 리스너를 비동기로 변경할 수 있음

비동기로 이벤트 리스너 등록방법(@Async, @EnableAsync)

@Async @EventListener public void handleAuthenticationSuccessEvent(AuthenticationSuccessEvent event){ Authentication authentication = event.getAuthentication(); log.info("Successful Authentication : {}", authentication); } @Configuration @EnableAsync public class WebMvcConfigure implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/me").setViewName("me"); registry.addViewController("/admin").setViewName("admin"); } }