인증 성공 또는 실패 발생했을 때 이벤트(ApplicationEvent)가 발생하고, 해당 이벤트에 관심있는 컴포넌트는 이벤트를 구독할 수 있다.
| 주의해야 할 부분!
| Spring의 이벤트 모델이 동기적이다!
| → 이벤트를 구독하는 리스너의 처리 지연은 이벤트를 발생시킨 요청의 응답지연에 직접적인 영향을 미친다.
이벤트 모델!
AbstractAuthenticationProcessingFilter
구현체로 UsernamePasswordAuthenticationFilter
가 있습니다.
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
sendEmail(authResult); //✨ Email 보내기(추가)
super.successfulAuthentication(request, response, authResult);
}
로그인 성공 시, 이메일을 보내야 된다면 위와 같이 만들 수 있습니다.
AuthenticationEventPublisher
가장 큰 장점은 Sprint Security 위에서 수정을 하지 않는다는 점입니다.
이렇게 되면 느슨한 결합도를 가지고 있고, 확장에 열려 있어 좋습니다!
(그저 리스너만 추가하면 되기 때문이죠?)
기본 구현체로는 DefaultAuthenticationEventPublisher
클래스가 사용됩니다.
인증에 성공하거나 실패할 때마다 각각 AuthenticationSuccessEvent
, AuthenticationFailureEvent
가 발생한다.
AuthenticationSuccessHandler
, AuthenticationFailureHandler
와 유사하고, 리스너는 서블릿 API와는 독립적으로 사용할 수 있다는 장점을 가집니다.
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
@Bean
public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}
}
@Slf4j
@Component
public class AuthenticationEvents {
@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 이벤트 모델이 동기적이기 때문에,
이벤트를 구독하는 리스너에서 처리가 지연되면 이벤트 발행하는 부분도 처리가 지연됨.
이게 무슨말이냐면!
@EventListener
public void handleAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
// ✨ 요부분!!!
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
Authentication authentication = event.getAuthentication();
log.info("Successful authentication result: {}", authentication.getPrincipal());
}
성공 이벤트 리스너에 5초정도 sleep 되도록 구문을 추가했습니다.
이렇게 하게 되면 로그인을 하게 되는데 까지 5초가 딜레이가 되버립니다.
즉 리스너에서 처리가 지연되어 이벤트도 함께 지연되는 것이지요!
이를 위한 해결법이 비동기 처리가 되도록 하는 것입니다.
(→ 다른 Thread에서 처리 되도록 하는 것이죠!)
@EnableAsync // ✨ 비동기를 활성화!
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
...
}
@Slf4j
@Component
public class AuthenticationEvents {
@Async // ✨ 비동기 처리!
@EventListener
public void handleAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
Authentication authentication = event.getAuthentication();
log.info("Successful authentication result: {}", authentication.getPrincipal());
}
}
위와 같이 @EnableAsync
로 비동기 처리 활성화, @Async
를 사용해서 비동기로 변경 가능합니다!