[Spring] Custom Error Handler

Sora Yoo·2023년 1월 26일

spring

목록 보기
1/2
post-thumbnail

안되는 게 더 많은 세상

API error handling을 위한 custom error handler

Standard Response Body

에러 response body는 자유롭게 구성할 수 있지만, 되도록이면 정해진 룰을 따르도록 표준화(RFC 7807) 된 response body를 구성한다.

{
    "type": "/errors/incorrect-user-pass",
    "title": "Incorrect username or password.",
    "status": 403,
    "detail": "Authentication failed due to incorrect username or password.",
    "instance": "/login/log/abc123"
}
	
  • type: 에러 분류에 따른 URI
  • title: 간단한 에러 메세지
  • status: HTTP response code
  • detail: 에러에 대한 설명
  • instance: 에러가 발생한 URI

ApiExceptionResponse

response body인 ApiExceptionResponse 클래스를 만들어준다.

@Getter
public class ApiExceptionResponse {
    private String type = "ERROR";
    private String title;
    private int status;
    private String detail;
    private String instance;

    public ApiExceptionResponse(HttpStatus httpStatus, String detail, String instance) {
        this.title = httpStatus.getReasonPhrase();
        this.status = httpStatus.value();
        this.detail = detail;
        this.instance = instance;
    }
}

Input

  • response body를 위의 표준화된 스키마에 따라 type, title, status, detail, instance로 구성해준다. (type은 임시적으로 "ERROR"로 고정)

constructor에서 받아야 하는 인자는

  • HttpStatus: 200, 401, 404, 500 등과 같은 status code
  • detail: error.getMessage()로 받아오는 string
  • instance: request uri를 받아오는 string 인자

Output

  • title: httpStatus.getReasonPhrase()는 "Unauthorized" 등의 타이틀을 받아온다.
  • status: httpStatus.value()는 200, 500 등의 int를 반환한다.
  • detail, instance: 아래 핸들러에서 error message를 보내 그대로 보여준다.

ErrorHandler

ApiExceptionResponse를 response에 담아 보내기 위한 함수를 작성

public class ErrorHandler {
    
    public static void handling(HttpServletResponse response, HttpStatus httpStatus, String message, String instance) 
        throws IOException, ServletException{
            response.setCharacterEncoding("UTF-8");
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setStatus(httpStatus.value());

            ObjectMapper objectMapper = new ObjectMapper();
            response.getWriter().write(objectMapper.writeValueAsString(
                new ApiExceptionResponse(httpStatus,message,instance)
            ));
    }
}

instance를 만들지 않고 Errorhandler.handling()로 호출하기 위해 static 함수로 구성한다.

Input

  • HttpServletResponse: handling()를 호출하는 함수의 response를 작성하여 건네기 위해 HttpServletRepsonse를 인자로 받아온다.
  • 위의 ApiExceptionResponse에 넘겨주기 위한 HttpStatus, message, instance를 인자로 받는다.

Output

  • response의 CharacterEncoding을 UTF-8로 지정한다.
  • json 타입을 주고받는 API response이기 때문에 response Content-type을 application/json으로 지정
  • Response body:
response.getWriter.write()

로 response body를 작성할 수 있다.

Spring에서 Object를 json으로 변환하는 것은 ObjectMapper이다.
ObjectMapper 인스턴스를 생성한 후, .writeValueAsString() 메소드를 통해 json을 작성한다.

ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().write(objectMapper.writeValueAsString(
	new ApiExceptionResponse(httpStatus,message,instance)
));

ErrorHandler 사용하기

아래는 BasicAuthenticationFilter를 상속한 JwtAuthroizationFilter에서 사용한 ErrorHandler 예시이다.

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    private MemberRepository memberRepository;

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager,
        MemberRepository memberRepository
    ) {
        super(authenticationManager);
        this.memberRepository = memberRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        try {
	            String jwtHeader = request.getHeader("Authorization");
				...
                ...
                ...
                chain.doFilter(request, response);
            }

        } 
        catch (NullPointerException | TokenExpiredException | SignatureVerificationException e) {
            ErrorHandler.handling(response, HttpStatus.UNAUTHORIZED, e.getMessage(), request.getRequestURI());
        }
    }
}

catch 구문에서 발생할 에러를 잡아 ErrorHandler.handling()으로 보내기

Input

  • response: 본 함수에서 처리 중인 HttpServletResponse
  • HttpSataus: 본 함수는 JWT Authorization을 하고 있기 때문에 Unauthorized status를 보낸다.
  • detail: ApiEsceptionResponse에서 detail을 구성하는 에러 메세지는 e.getMessage()로 잡을 수 있다.
  • instance: 요청이 온 uri는 request.getRequestURI()로 보낸다.

Output

Postman으로 API 요청을 보냈을 때 나타나는 response body

{
	"type": "ERROR",
    "title": "Unauthorized",
    "status": 401,
    "detail": "The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512",
    "instance": "/member/list"
}

0개의 댓글