1. 401로 잡혀버리는 커스텀 안된 예외들 → 잘못된 레거시 로직 수정
(1) 문제점
url이 잘못 됐을 때 스프링 기본 에러로 잡혔어야 할 404가 401 Unauthorized로 잡혀버린다.
또, 예외를 던지기만 하고 handler에서 예외를 잡아주지 않아
스프링 기본 에러로 잡혔어야 할 500도 마찬가지로 401 Unauthorized로 잡혀버린다.
에러 메시지론 “유효하지 않은 토큰입니다.”가 떴다.
즉, 현재 구조에선 예외 처리가 제대로 안 된 경우 스프링 기본 에러로 잡히지 않고 엉뚱한 곳에서 예외로 잡혀버린다.
(2) 해결책 : 스프링 기본 예외 처리(BasicErrorController) 동작하게 고치기
현재 예외 처리가 제대로 안 된 경우, 발생한 예외의 경로를 추적해보면 아래와 같다.
Controller
→ JwtAuthorizationFilter:71 (chain.doFilter)
→ ExceptionHandlerFilter:24 (chain.doFilter)
exceptionHandlerFilter 는 LogoutFilter 전에 있는 필터다.
SecurityConfig:45 (addFilterBefore(exceptionHandlerFilter(), LogoutFilter.class)
여기까진 좋은데… 그럼 왜 AuthenticationEntryPointCustom 에서 이 예외를 낚아채갈까?
@Slf4j
@Component
public class AuthenticationEntryPointCustom implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
log.warn("[AuthenticationEntryPointCustom] Unauthorized error : {}", authException.getMessage());
ErrorResponse errorResponse;
if (authException instanceof BadCredentialsException) {
errorResponse = new ErrorResponse(HttpStatus.UNAUTHORIZED, "아이디 혹은 비밀번호가 잘못되었습니다.");
} else {
errorResponse = new ErrorResponse(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다.");
}
ObjectMapper objectMapper = new ObjectMapper();
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
Java
복사
바로 레거시 코드에서 else문을 썼기 때문이다…..
여기서 else를 지워주면 마법같이 스프링이 기본적으로 제공하는 예외(BasicErrorController)가 뜬다.
잘못된 url을 입력하면 404가 뜨고, 예외를 제대로 잡지 못한 부분은 500이 뜬다.
@Slf4j
@Component
public class AuthenticationEntryPointCustom implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
if (authException instanceof BadCredentialsException) {
log.warn("[AuthenticationEntryPointCustom] Unauthorized error : {}",
authException.getMessage());
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.UNAUTHORIZED, "아이디 혹은 비밀번호가 잘못되었습니다.");
ObjectMapper objectMapper = new ObjectMapper();
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
}
Java
복사
패스
2. 각기 다른 에러 메시지 응답 형식 → 공통 응답 형식 사용
에러 메시지 응답 형식이 어떤 건 {code, status} 로 나가고, 어떤 건 {code, httpStatus}로 나간다. 통일해주자.
3. 각기 다른 예외 커스텀 → 일괄된 방식으로 커스텀
IllegalArgumentException 같은 기본 예외를 던지고, 후에 일괄적으로 이를 400 같은 커스텀 예외로 변환하는 코드들이 존재한다.
Agent findAgent = agentRepository.findById(request.getAgent_id())
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 현장요원입니다."));
Java
복사
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult IllegalArgumentException(IllegalArgumentException e) {
log.error("[IllegalArgumentExHandler] ex", e);
return new ErrorResult("400", e.getMessage());
}
Java
복사
이는 예외 처리의 정확성을 떨어트리고, 서버 내부 오류인지 클라이언트 단의 오류인지 헷갈리게 하는 등 혼란을 줄 수 있다.
이 방식은 없애자. 이 코드들도 커스텀 예외를 던지게 해서, 서버 내부 오류와 커스텀 예외를 구분해주자.
Agent findAgent = agentRepository.findById(request.getAgent_id())
.orElseThrow(() -> new AgentException(AgentErrorResult.NOT_FOUND_AGENT));
Java
복사
public enum AgentErrorResult implements ErrorResult {
NOT_FOUND_AGENT(HttpStatus.BAD_REQUEST, "존재하지 않는 현장요원입니다.")
Java
복사
4. 일반적이지 않은 상태 코드들 → 상황에 맞는 상태 코드 사용
I_AM_A_TEAPOT 이란 상태 코드를 들어 보았는가..?
이름은 정말 귀엽지만, 우리 레거시 코드에선 이 상태 코드를 NOT_FOUND를 써야 할 상황에서 쓰고 있다.
왜…. 라는 의문이 드는 일반적이지 않은 상태 코드들은 일반적이게 바꿔주자.
팀 내 규칙을 정하고 따르자. (클라이언트 분들도 함께 정하면 좋다)
우리 팀은 아래와 같이 설정했다.
Status Code
•
OK: 200 → 성공적으로 요청을 처리한 경우
•
CREATED: 201 → 새로운 리소스가 생성된 경우
•
NO_CONTENT: 204 → body에 담아보낼 게 없는 경우 #삭제API와 관련
•
BAD_REQUEST: 400 → 매개변수 누락과 같이 오류의 원인이 클라이언트의 요청과 관련된 경우
(쿼리 스트링 관련)
•
UNAUTHORIZED: 401 → 세션 정보를 제공하지 않았거나 올바르지 않은 세션 정보인 경우
(로그인 제외 모든 API와 관련)
•
FOR_BIDDEN: 403 → 접근 권한이 없는 경우
(관리자 API와 관련)
•
NOT_FOUND: 404 → 요청한 URL을 찾을 수 없거나 존재하지 않는 자원인 경우
(쿼리파라미터와 관련)
•
CONFLICTED: 409 → 중복되는 자원인 경우
(중복검사와 관련)
•
INTERNAL_SERVER_ERROR: 500