Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- 토글 그룹
- 영상 프레임 추출
- 유니티
- 문자열 압축
- 탄막 이동
- MySQL
- 마우스 따라다니기
- c#
- 우분투
- AI Hub
- 원형
- 탄막 스킬 범위
- 알고리즘
- 그리디알고리즘
- 백준
- 알고리즘 목차
- 윈도우
- 2020 KAKAO BLIND RECRUITMENT
- 수 만들기
- 탄막
- SWEA
- 걷는건귀찮아
- 단어 수학
- 18249
- 3273
- 강의실2
- mysqld.sock
- 회의실 배정
- 자료구조 목차
- 3344
Archives
- Today
- Total
와이유스토리
[도트타이머] 6. 예외처리(CustomException, ExceptionHandler, JwtAuthenticationEntryPoint, JwtAccessDeniedHandler) 본문
프로젝트/백엔드
[도트타이머] 6. 예외처리(CustomException, ExceptionHandler, JwtAuthenticationEntryPoint, JwtAccessDeniedHandler)
유(YOO) 2022. 12. 22. 22:07CustomException
CustomException 생성 방법에는 2가지가 있다.
1. CustomException 클래스 한 개에 Enum으로 ErrorCode 여러 개 사용
2. CustomException 클래스를 상속받는 클래스 여러 개 생성
1. CustomException 클래스 한 개에 Enum으로 ErrorCode 여러 개 사용
클래스 여러 개 생성하지 않아도 되므로 간편
package com.dotetimer.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException{
ErrorCode errorCode;
}
package com.dotetimer.infra.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
/* Exception
- RuntimeException
400 NullPointException
400 IllegalArgumentException
400 IllegalStateException
400 MethodArgumentNotValidException
404 NotFoundException -> Service
404 NoHandlerFoundException
405 HttpRequestMethodNotSupportedException
415 HttpMediaTypeException
500 Exception
500 RuntimeException
ConstraintViolationException
MissingArgumentTypeMismatchException
DateTimeParseException
- Custom
401 UnauthorizedException -> Controller
401 AccessDeniedException
401 AuthenticationEntryPoint
403 AccessDeniedHandler
404 NotFoundDataException
DuplicateException
NoSuchDataException
InvalidReqParamException
InvalidReqBodyException
S3Exception
*/
@Getter
@AllArgsConstructor
public enum ErrorCode {
// 400 BAD_REQUEST : 잘못된 요청
INVALID_LOGIN(HttpStatus.BAD_REQUEST, "이메일이 잘못되거나 비밀번호 길이가 8자 미만입니다"),
INVALID_EMAIL(HttpStatus.BAD_REQUEST, "올바른 형식의 이메일 주소여야 합니다"),
INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "refresh token이 유효하지 않습니다"),
INVALID_DATA(HttpStatus.BAD_REQUEST, "잘못된 데이터입니다"),
LIMIT_DATA(HttpStatus.BAD_REQUEST, "데이터 저장이 제한되었습니다"),
MISMATCH_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "refresh token의 유저 정보가 일치하지 않습니다"),
CANNOT_FOLLOW_MYSELF(HttpStatus.BAD_REQUEST, "자기 자신은 팔로우할 수 없습니다"),
// 401 UNAUTHORIZED : 인증되지 않은 사용자
INVALID_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "인증 정보가 없는 토큰입니다"), // 토큰 잘못된 경우(시그니처 불일치)
EXPIRE_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다"), // 토큰 만료된 경우
// 403 FORBIDDEN : 권한 제한 사용자
FORBIDDEN(HttpStatus.FORBIDDEN, "권한이 없는 요청입니다"),
// 404 NOT_FOUND : Resource를 찾을 수 없음
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 유저 정보를 찾을 수 없습니"),
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 리소스를 찾을 수 없습니다"),
PASSWORD_NOT_FOUND(HttpStatus.NOT_FOUND, "비밀번호가 틀렸습니다"),
REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "refresh token을 찾을 수 없습니다"),
NOT_FOLLOW(HttpStatus.NOT_FOUND, "팔로우 중이지 않습니다"),
// 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재
DUPLICATE_RESOURCE(HttpStatus.CONFLICT, "이미 존재하는 데이터입니다");
private final HttpStatus httpStatus;
private final String detail;
}
package com.dotetimer.exception;
import lombok.Builder;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.time.LocalDateTime;
@Getter
@Builder
public class ErrorResponse {
private final LocalDateTime timestamp = LocalDateTime.now();
private final int status;
private final String error;
private final String message;
}
2. CustomException 클래스를 상속받는 클래스 여러 개 생성
package com.dotetimer.exception;
import lombok.Getter;
public class CustomException extends RuntimeException{
@Getter
String name;
public CustomException(String message) {
super(message);
}
}
package com.dotetimer.exception;
public class NotFoundDataException extends CustomException{
NotFoundDataException(String message) {
super(message);
name = "NotFoundDataException";
}
}
ExceptionHandler
1. 컨트롤러마다 ExceptionHandler 생성
2. 모든 컨트롤러에서 사용할 수 있는 GeneralExceptionHandler
1번은 번거로우니 2번으로 만들었다.
package com.dotetimer.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.crossstore.ChangeSetPersister.NotFoundException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
@Slf4j
@ControllerAdvice
public class GeneralExceptionHandler {
// Response 생성
private ResponseEntity<ErrorResponse> newResponse(String message, HttpStatus status) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json");
ErrorResponse errorResponse = ErrorResponse.builder()
.status(status.value())
.error(status.name())
.message(message)
.build();
return new ResponseEntity<>(errorResponse, httpHeaders, status);
}
// CustomException 처리
@ExceptionHandler({CustomException.class})
public ResponseEntity<?> handleCustomException(CustomException e) {
log.info(e.getMessage());
return newResponse(e.getErrorCode().getDetail(), e.getErrorCode().getHttpStatus());
}
// 400 Bad Request 처리
@ExceptionHandler({NullPointerException.class, IllegalArgumentException.class, IllegalStateException.class, MethodArgumentNotValidException.class, DuplicateKeyException.class})
public ResponseEntity<?> handleBadRequestException(Exception e) {
log.info(e.getMessage());
if (e instanceof MethodArgumentNotValidException) {
return newResponse(((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST);
}
return newResponse(e.getMessage(), HttpStatus.BAD_REQUEST);
}
// 404 Not Found 처리
@ExceptionHandler({NoHandlerFoundException.class, NotFoundException.class})
public ResponseEntity<?> handleNotFoundException(Exception e) {
log.info(e.getMessage());
return newResponse(e.getMessage(), HttpStatus.NOT_FOUND);
}
// 404 Method Not Allowed 에러
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public ResponseEntity<?> handleMethodNotAllowedException(Exception e) {
log.info(e.getMessage());
return newResponse(e.getMessage(), HttpStatus.METHOD_NOT_ALLOWED);
}
// 415 Unsupported Media Type 에러
@ExceptionHandler({HttpMediaTypeException.class})
public ResponseEntity<?> handleHttpMediaTypeException(Exception e) {
log.info(e.getMessage());
return newResponse(e.getMessage(), HttpStatus.UNSUPPORTED_MEDIA_TYPE);
}
// 500 Internal Server Error 에러
@ExceptionHandler({Exception.class, RuntimeException.class})
public ResponseEntity<?> handleException(Exception e) {
log.info(e.getMessage());
return newResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Filter에서 던지는 에러
JwtAuthenticationFilter가 던지는 에러를 처리하기 위한 클래스들이다. 다른 블로그를 참고하니 Custom Exception은 Spring의 영역이나, Spring Security는 Spring 이전에 필터링 하므로 아무리 Security단=에서 예외가 발생해도 Spring의 DispatcherServlet까지 닿을 수가 없다고 한다.
JwtAuthenticationEntryPoint.class
package com.dotetimer.exception;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import static com.dotetimer.exception.ErrorCode.EXPIRE_AUTH_TOKEN;
import static com.dotetimer.exception.ErrorCode.INVALID_AUTH_TOKEN;
// 401 Unauthorized Exception 처리(인증 실패)
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
ErrorResponse errorResponse;
if (authException.equals(EXPIRE_AUTH_TOKEN)) {
errorResponse = ErrorResponse.builder()
.status(EXPIRE_AUTH_TOKEN.getHttpStatus().value())
.error(EXPIRE_AUTH_TOKEN.getHttpStatus().name())
.message(EXPIRE_AUTH_TOKEN.getDetail())
.build();
}
else {
errorResponse = ErrorResponse.builder()
.status(INVALID_AUTH_TOKEN.getHttpStatus().value())
.error(INVALID_AUTH_TOKEN.getHttpStatus().name())
.message(INVALID_AUTH_TOKEN.getDetail())
.build();
}
JSONObject json = new JSONObject();
json.put("timestamp", errorResponse.getTimestamp());
json.put("status", errorResponse.getStatus());
json.put("error", errorResponse.getError());
json.put("message", errorResponse.getMessage());
json.put("path", request.getRequestURI());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("Content-Type", "application/json");
response.setCharacterEncoding("utf-8");
// response.getWriter().write("401 Exception");
// response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().print(json);
response.getWriter().flush();
response.getWriter().close();
response.sendRedirect("/api/user/sign_in"); // 마지막 순서 주의
log.info(json.toString());
log.error(authException.getMessage());
}
}
JwtAccessDeniedHandler.class
package com.dotetimer.exception;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
import org.json.JSONObject;
import static com.dotetimer.exception.ErrorCode.FORBIDDEN;
// 403 Forbidden Exception 처리(권한 제한)
@Slf4j
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ErrorResponse errorResponse;
errorResponse = ErrorResponse.builder()
.status(FORBIDDEN.getHttpStatus().value())
.error(FORBIDDEN.getHttpStatus().name())
.message(FORBIDDEN.getDetail())
.build();
JSONObject json = new JSONObject();
json.put("timestamp", errorResponse.getTimestamp());
json.put("status", errorResponse.getStatus());
json.put("error", errorResponse.getError());
json.put("message", errorResponse.getMessage());
json.put("path", request.getRequestURI());
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader("content-type", "application/json"); // response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(json);
response.getWriter().flush();
response.getWriter().close();
log.info(json.toString());
log.error(accessDeniedException.getMessage());
}
}
* 참고
'프로젝트 > 백엔드' 카테고리의 다른 글
[도트타이머] 7. QueryDSL 탐험기 - JPA 객체 지향 쿼리(정적 쿼리, 동적 쿼리, QueryDSL 설치 및 사용) (0) | 2022.12.23 |
---|---|
[도트타이머] 4. JPA 폴더링 및 간단한 회원가입 구현(보안X) (0) | 2022.12.14 |
[도트타이머] 3. API 명세서 작성 (0) | 2022.12.13 |
[도트타이머] 2. 스프링부트에 MySQL 연동 (0) | 2022.12.12 |
[도트타이머] 1. ERD모델 만들기(aka. 처음 설계할 때 잘하자) (0) | 2022.12.11 |
Comments