이번 포스팅은 예외 핸들러를 등록하는 과정을 다뤄보겠다.
Spring Security에서는 기본적으로 2가지 예외(인증/인가)에 대한 처리를 한다. 각각 어떻게 처리되는지 알아보자.
AuthenticationException
은 인증 과정에서 유효하지 않은 자격 증명이 제출될 때(로그인 실패) 발생한다.
UsernamePasswordAuthenticationFilter
에서 자격증명이 유효하지 않을 경우 AuthenticationException을 발생시킴ExceptionTranslationFilter
는 예외를 캐치하고 AuthenticationEntryPoint
를 통해 처리.AccessDeniedException
은 인증된 사용자가 자신이 권한이 없는 리소스에 접근할 때 발생한다.
FilterSecurityInterceptor
는 접근 결정을 내리는 필터로, 권한이 충분하지 않을 경우 AccessDeniedException
을 발생시킴ExceptionTranslationFilter
는 예외를 캐치하고 AccessDeniedHandler
를 통해 처리.이제 각 예외들이 AuthenticationEntryPoint
와 AccessDeniedHandler
를 통해 처리됨을 알게 되었다.
커스텀 예외 핸들러를 등록하기 전에, 먼저 인증 관련 에러 상황에 쓰이는 HTTP 상태 코드에 대해 알아보자.
나는 기본 핸들러가 아닌 커스텀 핸들러를 통해 각 인증 예외 상황 발생시 적절한 에러 응답을 보내는 목적을 갖고 있다. 이제 각 예외 상황에 대해서 어떻게 처리할지에 대해 생각해보자.
401 에러
는 인증되지 않음을 나타내는 에러이다. 설정한 Security FilterChain
에 따라 클라이언트가 유효한 토큰을 갖고 API 요청을 가져오지 않으면 해당 상태 코드를 보낼 것이다.
403 에러
는 금지됨을 나타내는 에러이다. 401 에러가 인증이 안됨을 나타내는 상태 코드라면, 403 에러는 인증은 되었지만 권한이 없는 경우를 뜻한다.
FilterChain 구성시 http.authorizeRequests 메서드를 통해 특정 경로에 대해 권한 설정을 할 수 있는데.. 아래 코드의 경우 사용자가 HOST 권한이 없다면 403 에러를 받게 될 것이다.
.requestMatchers(antMatcher(HttpMethod.POST, "/api/posts")).hasRole("HOST")
이제 각 예외 상황들에 적절한 상태 코드를 보내 보자.
각 핸들러들은 응답으로 적절한 상태코드와 메시지를 보내게 될 것이다.
// 인증 실패 (401)
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ResponseWriter responseWriter;
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authenticationException) throws IOException {
responseWriter.writeErrorResponse(response, SC_UNAUTHORIZED, UNAUTHORIZED.getMessage());
}
}
// 인증이 되었지만 접근 권한이 없는 경우 (403)
@RequiredArgsConstructor
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
private final ResponseWriter responseWriter;
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
responseWriter.writeErrorResponse(response, SC_FORBIDDEN, NO_AUTHORITY.getMessage());
}
}
구현한 핸들러들은 FilterChain
에서 exceptionHandling 메서드를 통해 등록할 수 있다.
.exceptionHandling()
.accessDeniedHandler(new JwtAccessDeniedHandler(responseWriter))
.authenticationEntryPoint(new JwtAuthenticationEntryPoint(responseWriter))