개인적으로 학습한 내용을 바탕으로 작성된 글입니다.
잘못된 정보가 포함되어 있을 수 있습니다.
Security 필터에 의해 발생하는 예외는 2가지가 있다.
인증(Authentication)에 대해서는 AuthenticationException
이 발생하고,
인가(Authorization)에 대해서는 AccessDeniedException
이 발생한다.
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
....
// 예외 핸들링 메서드
http.exceptionHandling()
.authenticationEntryPoint() // 인증 예외 핸들러 지정
.accessDeniedHandler(); // 인가 예외 핸들러 지정
}
}
예외를 핸들링할 핸들러를 지정할 수 있으며, 따로 설정하지 않으면 컨테이너에 기본으로 등록되어 있는 핸들러가 동작하게 된다.
인증 예외는 AuthenticationEntryPoint
인터페이스를 구현한 핸들러를 받을 수 있고 인가 예외는 AccessDeniedHandler
인터페이스를 구현한 핸들러를 받을 수 있다.
CustomAuthenticationEntryPoint
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
private ObjectMapper mapper = new ObjectMapper();
@Override
void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
String accept = request.getHeader("Accept");
if ("application/json".equals(accept) {
ErrorResponse error = ErrorResponse.builder()
.code("401")
.message("인증이 필요합니다.")
.build();
String result = mapper.writeValueAsString(error);
response.setStatus(401);
response.getWriter().write(result);
}
....
}
}
commence
메서드를 오버라이딩하여 들어온 요청에 대해서 적절하게 예외 처리 로직을 구현하면 된다.
CustomAccessDeniedHandler
public interface CustomAccessDeniedHandler implements AccessDeniedHandler {
private ObjectMapper mapper = new ObjectMapper();
@Override
void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
String accept = request.getHeader("Accept");
if ("application/json".equals(accept) {
ErrorResponse error = ErrorResponse.builder()
.code("403")
.message("접근 권한이 없습니다.")
.build();
String result = mapper.writeValueAsString(error);
response.setStatus(403);
response.getWriter().write(result);
}
....
}
}
handle
메서드를 오버라이딩하여 들어온 요청에 대해서 적절하게 예외 처리 로직을 구현하면 된다.
주의. 잘못된 내용 및 아키텍쳐 구조일 수 있습니다.
기본적으로, @ExceptionHandler
는 컨트롤러에서 발생한 예외에 동작하기 때문에 필터에서 발생하는 Security 예외는 잡아낼 수 없다.
하지만, 앞서 정의했던 커스텀 핸들러 클래스를 통해서 예외 처리를 HandlerExceptionResolver
에게 위임하면 @ExceptionHandler
로 처리할 수가 있다.
public interface CustomAccessDeniedHandler implements AccessDeniedHandler {
// 1.
private final HandlerExceptionResolver resolver;
// 2.
public CustomAccessDeniedHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolve = resolver;
}
@Override
void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
String accept = request.getHeader("Accept");
if ("application/json".equals(accept) {
// 3.
resolver.resolve(request, response, null, accessDeniedException)
}
....
}
}
HandlerExceptionResolver
를 주입 받는다.@Qualifier
를 사용하여 특정 Bean을 지정해야 한다.설계상 괜찮은 방식인지는 잘 모르겠으나, 필자는 가능한 한 모든 예외를 한 곳에서 처리하고자 @ExceptionHandler
를 통해서 예외를 해결하도록 작성했다.