토이 프로젝트에서 세션 로그인 인증을 통해 외부인이 접근할 경우 아래와 같은 오류 화면이 표시되는 서비스가 있습니다.
이 서비스 기능을 페이지 컨트롤러마다 코드를 추가하는 대신, 이를 한 곳에서 처리하기 위해 Filter를 도입해보았습니다.
밑의 코드는 세션에 있는 'account'와 'access'를 검증하여 커스텀 예외를 발생시키는 코드입니다.
public class AuthChecker {
public void blockOutsiders(HttpSession session) throws CustomException {
if (session.getAttribute("account") == null){
throw new CustomException(AuthErrorCode.ACCOUNT_NOT_FOUND);
} else if (session.getAttribute("access").hashCode() == 0) {
throw new CustomException(AuthErrorCode.AUTH_ACCESS_NOT_FOUND);
}
}
}
필터 (Filter) 서블릿으로 들어오는 요청과 서블릿에서 나가는 응답을 가로채서 조작하거나 처리하는 역할을 합니다.
세션으로 사용자 인증을 통해 예외를 발생시키는 필터를 만들어보겠습니다.
'PageRequestAuthFilter' 클래스를 생성하여 'OncePerRequestFilter'를 상속받아 줍니다.
그 다음 'doFilterInternal' 메서드에 위에 적어준 (authChecker.blockOutsiders(session)) 코드를 참고하여 아래와 같이 작성하였습니다.
public class PageRequestAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpSession session = request.getSession(false);
log.info("[R] 사용자 인증 필터 실행");
if (session == null || session.getAttribute("account") == null) {
log.error("[!] 사용자 인증 실패");
throw new CustomException(AuthErrorCode.ACCOUNT_NOT_FOUND);
} else if (session.getAttribute("access").hashCode() == 0) {
log.error("[!] 사용자 승인 인증 실패");
throw new CustomException(AuthErrorCode.AUTH_ACCESS_NOT_FOUND);
}
log.info("[S] 사용자 인증 성공");
filterChain.doFilter(request, response);
}
필터를 사용하기 위해 'WebConfig' 클래스에 'Bean'을 등록하는 코드를 작성합니다.
@Bean
public FilterRegistrationBean<PageRequestAuthFilter> pageRequestAuthFilterRegistration() {
FilterRegistrationBean<PageRequestAuthFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new PageRequestAuthFilter()); // 필터 등록
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 필터 순서
registrationBean.addUrlPatterns("/page/user/*"); // URL 패턴
return registrationBean;
}
세션 인증으로 예외를 발생시키는 필터가 구축되었습니다.
그러나 예외가 발생하더라도 @ControllerAdvice 어노테이션이 작동하지 않는 상황이 있을 수 있습니다.
@ControllerAdvice란? 모든 @Controller 전역에서 발생할 수 있는 예외를 잡아 처리해주는 annotation
위에서 예외가 발생하지만 @ControllerAdvice 어노테이션이 작동하지 않는 상황을 마주했습니다.
@ControllerAdvice는 컨트롤러 계층에서 발생한 예외에 대한 처리를 담당하기 때문입니다.
필터는 컨트롤러에 이르기 전의 단계에서 동작하므로, 필터에서 발생한 예외에 대해서는 @ControllerAdvice의 예외 처리가 동작하지 않습니다.
요청 -> 서블릿 -> 필터 -> 컨트롤러 -> 클라이언트 응답
따라서 필터에서 예외 처리를 하기 위해서는 해당 필터 내에서 예외 처리 로직을 직접 구현해보겠습니다.
'PageRequestAuthFilter' 클래스에서 'doFilterHandleException' 메소드를 작성합니다.
private void doFilterHandleException(CustomException e, HttpServletResponse response) throws IOException {
log.error("[!] Error Code : " + e.getErrorCode());
log.error("[!] Error Message : " + e.getMessage());
String redirectUrl = "/error/view?error=" + e.getErrorCode().name()
+ "&code=" + e.getErrorCode().code()
+ "&message=" + URLEncoder.encode(e.getErrorCode().getMessage(), StandardCharsets.UTF_8);
response.sendRedirect(redirectUrl);
}
마지막으로 try catch를 통해 예외 처리를 진행하였습니다.
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpSession session = request.getSession(false);
log.info("[R] 사용자 인증 필터 실행");
try {
if (session == null || session.getAttribute("account") == null) {
log.error("[!] 사용자 인증 실패");
throw new CustomException(AuthErrorCode.ACCOUNT_NOT_FOUND);
} else if (session.getAttribute("access").hashCode() == 0) {
log.error("[!] 사용자 승인 인증 실패");
throw new CustomException(AuthErrorCode.AUTH_ACCESS_NOT_FOUND);
}
log.info("[S] 사용자 인증 성공");
filterChain.doFilter(request, response);
} catch (CustomException e) {
doFilterHandleException(e, response);
}
}
그럼 사용자 인증에 따른 오류 페이지가 정상적으로 나오는걸 확인할수있습니다.
log
ACCOUNT_NOT_FOUND("A0001", "등록된 계정이 없거나 비정상적인 접근입니다."),