Spring Security 인증 이벤트

midas·2022년 5월 21일
0

인증 이벤트?

인증 성공 또는 실패 발생했을 때 이벤트(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를 사용해서 비동기로 변경 가능합니다!

Security Exception → Event 종류

  • BadCredentialsException → AuthenticationFailureBadCredentialsEvent
    비밀번호 불일치
  • UsernameNotFoundException → AuthenticationFailureBadCredentialsEvent
    계정없음
  • AccountExpiredException → AuthenticationFailureExpiredEvent
    계정만료
  • ProviderNotFoundException → AuthenticationFailureProviderNotFoundEvent
  • DisabledException → AuthenticationFailureDisabledEvent
    계정 비활성화
  • LockedException → AuthenticationFailureLockedEvent
    계정잠김
  • AuthenticationServiceException → AuthenticationFailureServiceExceptionEvent
  • CredentialsExpiredException → AuthenticationFailureCredentialsExpiredEvent
    비밀번호 만료
  • InvalidBearerTokenException → AuthenticationFailureBadCredentialsEvent

🔖 참고

profile
BackEnd 개발 일기

0개의 댓글