Spring Security에서 인증/인가 시 발생하는 Exception을 정리해보았다.
인증/인가 시 예외가 발생하는 ExceptionTranslationFilter라는 필터가 이를 감지하고, AuthenticationException, AccessDeniedException라는 예외를 보낸다.
AuthenticationException은 인증 시 발생하는 예외,
AccessDeniedException은 인가 시 발생하는 예외이다.
AuthenticationEntryPoint
인증 과정에서 예외가 발생하는 경우, Spring Security는 AuthenticationEntryPoint라는 핸들러를 통해 처리한다.
public interface AuthenticationEntryPoint {
void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException;
}
AuthenticationEntryPoint는 AuthenticationException을 처리하는 핸들러 인터페이스로, 이를 구현한 LoginUrlAuthenticationEntryPoint, BasicAuthenticationEntryPoint 등이 있다.
인증 예외 발생 시 이를 사용자 정의로 처리하고 싶을 때가 있는데, 그럴 때 아래와 같이 AuthenticationEntryPoint의 구현체를 만들고 이를 security config에 등록하면 된다.
public class CustomBasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setHeader("error-reason", "Authentication failed");
response.setError(HttpStatus.UNAUTORHIZED.value(), authException.getMessage());
}
}
위와 같이 오버라이딩한 commence 메서드에 내가 원하는 응답 및 로직을 작성하면 된다. 위 코드에서는 예외 발생 시 보내는 응답의 헤더, 에러 내용 등을 커스텀하였다.
인증 예외 발생 시 HTTP status는 401을 보내므로, UNAUTHROIZED로 설정하였다.
이후 아래와 같이 security config에 이를 추가해준다.
http.httpBasic(hbc -> hbc.autheneticationEntryPoint(new CustomBasicAuthenticationEntryPoint()));
위 코드는 httpBasic에 설정한 것으로, httpBasic은 로그인 과정과 연관이 있어 로그인 과정시 인증 예외가 발생하면 이를 내가 정의한 핸들러가 처리할 수 있다.
만약 로그인 과정 뿐만 아니라 프로젝트 전역에서 인증 예외를 처리하고 싶다면 아래와 같이 config 파일을 작성하면 된다.
http.exceptionHandling(ehc -> ehc.authenticationEntryPoint(new CustomBasicAuthenticationEntryPoint()));
AccessDeniedHandler
다음은 인가 과정에서 발생하는 accessDeniedException을 처리하는 핸들러이다.
public interface AccessDeniedHandler {
void handle(HttpServletRequest request, HttpServletResponse respnse, AccessDeniedException accessDeniedException) throws IOException, ServletException;
이 역시 핸들러 인터페이스이고, 구현체로 AccessDeniedHandlerImpl이 존재한다.
인가 과정에서도 인증 예외 발생 시 사용자 정의로 핸들러를 구성한 것과 동일하게 핸들러를 만들 수 있다.
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpSevletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, SevletException {
response.setHeader("denied-reason", "Authorization failed");
response.setStatus(HttpStatus.FORBIDDEN.value());
}
}
위 처럼 AccessDeniedHandler를 구현한 클래스를 만들어 주고, security config에 추가하면 인가 예외 시 내가 구현한 핸들러 메서드가 작동하게 할 수 있다.
http.exceptionHandling(ehc -> ehc.accessDeniedHandler(new CustomAccessDeniedHandler()));
참고로 AccessDeniedHandler는 인가 과정처럼 HttpBasic에서는 설정할수 없는데, 인가는 기본적으로 인증이 완료가 된 상태라 로그인과 관련된 HttpBasic는 인증 과정을 진행하고, 그 이후 인가를 확인하기 때문이다.
Authentication Event
위에서 인증/인가 과정 시 발생하는 예외를 처리하는 방식을 설명했는데, 인증 과정에서는 인증 성공/실패에 따른 Event가 존재한다.
인증 성공 시 AuthenticationSuccessEvent,
인증 실패 시 AbstractAuthenticationFailureEvent가 발생하고, S
pring Security에서는 DefaultAuthenticationPubliser가 해당 이벤트를 listener로 발행한다.
EventListener를 구현하므로써 인증 성공/실패 시 필요한 로직들을 수행하게끔 할 수 있다.
@Component
@Slf4j
public class AuthenticationEvents {
@EventListener
public void onSuccess(AuthenticationSuccessEvent success) {
log.info("Login successful for the user : {}",
success.getAuthentication().getName());
}
@EventListener
public void onFailure(AbstractAuthenticationFailureEvent failures) {
log.error("Login failed for the user : {} due to : {}",
failures.getAuthentication().getName(),
failures.getException().getMessage());
}
}
Event Listener로 등록하기 위해선 @EventListener 어노테이션을 메서드에 추가하고, 처리하고 싶은 이벤트를 함수 인자로 선언해주면 된다.