From 946109c10d6cf8d092abce6eb7cdd4559af520d3 Mon Sep 17 00:00:00 2001 From: rong5026 Date: Wed, 22 Jan 2025 10:08:07 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20response=20=EC=A0=84=EB=8B=AC=20?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=84=EC=97=AD=20response=EB=A1=9C=20=EA=B0=90?= =?UTF-8?q?=EC=8B=B8=EC=84=9C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/constants/WebSecurityURI.java | 3 +- .../global/response/GlobalResponse.java | 15 ++++++ .../global/response/GlobalResponseAdvice.java | 48 +++++++++++++++++++ .../src/main/resources/application.yml | 7 ++- 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java create mode 100644 application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponseAdvice.java diff --git a/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java b/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java index 6812b3c..0057280 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java +++ b/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java @@ -12,7 +12,8 @@ public final class WebSecurityURI { "/auth/login/oauth2/code/google", "/swagger-ui/**", "/v3/api-docs/**", - "/swagger-resources/**" + "/swagger-resources/**", + "/common/health/**" ); public static final List CORS_ALLOW_URIS = diff --git a/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java new file mode 100644 index 0000000..fa0e869 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java @@ -0,0 +1,15 @@ +package org.mainapplication.global.response; + +import java.time.LocalDateTime; + +import org.mainapplication.global.error.ErrorResponse; + +public record GlobalResponse(boolean success, int status, Object data, LocalDateTime timestamp) { + public static GlobalResponse success(int status, Object data) { + return new GlobalResponse(true, status, data, LocalDateTime.now()); + } + + public static GlobalResponse fail(int status, ErrorResponse errorResponse) { + return new GlobalResponse(false, status, errorResponse, LocalDateTime.now()); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponseAdvice.java b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponseAdvice.java new file mode 100644 index 0000000..800a122 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponseAdvice.java @@ -0,0 +1,48 @@ +package org.mainapplication.global.response; + +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import jakarta.servlet.http.HttpServletResponse; + +@RestControllerAdvice(basePackages = "org.application") +public class GlobalResponseAdvice implements ResponseBodyAdvice { + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + return true; + } + + @Override + public Object beforeBodyWrite( + Object body, + MethodParameter returnType, + MediaType selectedContentType, + Class selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response) { + + HttpServletResponse servletResponse = + ((ServletServerHttpResponse)response).getServletResponse(); + + // http 상태 코드 + int status = servletResponse.getStatus(); + HttpStatus resolve = HttpStatus.resolve(status); + + // 상태 코드가 null이거나 응답 바디가 문자열인 경우 원본 응답을 반환 + if (resolve == null || body instanceof String) { + return body; + } + + // 2xx 범위인 경우 응답 처리 + if (resolve.series() == HttpStatus.Series.SUCCESSFUL) { + return GlobalResponse.success(status, body); + } + return body; + } +} \ No newline at end of file diff --git a/application/main-application/src/main/resources/application.yml b/application/main-application/src/main/resources/application.yml index 3f4bb82..50839e8 100644 --- a/application/main-application/src/main/resources/application.yml +++ b/application/main-application/src/main/resources/application.yml @@ -23,7 +23,12 @@ logging: level: org: springframework: - security=DEBUG: + security: trace + web: DEBUG + hibernate: + orm: + jdbc: + bind: trace server: servlet: From 729d1c3c8afa1e8901945b5cdc8aed852c49642d Mon Sep 17 00:00:00 2001 From: rong5026 Date: Wed, 22 Jan 2025 11:45:14 +0900 Subject: [PATCH 2/7] =?UTF-8?q?docs:=20=EC=A0=9C=EA=B1=B0=EB=90=9C=20commo?= =?UTF-8?q?n=20=EB=AA=A8=EB=93=88=20=EA=B4=80=EB=A0=A8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/main-application/build.gradle | 10 ++++++++++ settings.gradle | 3 --- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/application/main-application/build.gradle b/application/main-application/build.gradle index 911bc8d..5158f0a 100644 --- a/application/main-application/build.gradle +++ b/application/main-application/build.gradle @@ -28,10 +28,20 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3' } + bootJar { archiveFileName = 'yapp-main-application.jar' + enabled = true // 실행 가능한 모듈에서만 활성화 +} + +jar { + enabled = false } tasks.named('test') { useJUnitPlatform() } + +springBoot { + mainClass = "org.mainapplication.MainApplication" // MainApplication 클래스의 패키지 경로 +} diff --git a/settings.gradle b/settings.gradle index 451b765..7311591 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,9 +4,6 @@ rootProject.name = 'yapp' include 'application:main-application' include 'application:upload-schedule-application' -// common 모듈 -include 'common:common-module' - // system domain 모듈 include 'domain:domain-module' From 43e191dc4172cb8725baaf004c419cebb432f7d9 Mon Sep 17 00:00:00 2001 From: rong5026 Date: Wed, 22 Jan 2025 11:47:20 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20JWT=20=ED=95=84=ED=84=B0=EA=B0=80=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20@Component=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mainapplication/global/filter/JwtAuthenticationFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java b/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java index 5c4ce08..0c6ad54 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java +++ b/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java @@ -7,7 +7,6 @@ import org.mainapplication.global.util.JwtUtil; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import jakarta.servlet.FilterChain; @@ -16,7 +15,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -@Component +// @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { From df0bbcc0e068d5422133a63928162138a8ab489f Mon Sep 17 00:00:00 2001 From: rong5026 Date: Wed, 22 Jan 2025 11:49:12 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20oauth2=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EA=B0=92=20=EC=B2=98=EB=A6=AC=20=EC=9E=AC=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - oauth2 로그인 실패시 서비스 url로 리다이렉트 - 예외발생 시 globalResponse로 에러문구 리턴 --- .../global/config/WebSecurityConfig.java | 13 ++---- .../CustomAuthenticationEntryPoint.java | 40 +++++++++++++++++++ .../handler/CustomOAuth2FailureHandler.java | 14 ++++--- 3 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomAuthenticationEntryPoint.java diff --git a/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java b/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java index 0f94cec..d9672c3 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java +++ b/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java @@ -7,6 +7,7 @@ import org.mainapplication.global.constants.WebSecurityURI; import org.mainapplication.global.filter.JwtAuthenticationFilter; import org.mainapplication.global.filter.TestAuthenticationFilter; +import org.mainapplication.global.oauth2.handler.CustomAuthenticationEntryPoint; import org.mainapplication.global.oauth2.handler.CustomOAuth2FailureHandler; import org.mainapplication.global.oauth2.handler.CustomOAuth2SuccessHandler; import org.mainapplication.global.oauth2.service.CustomOauth2UserService; @@ -36,6 +37,7 @@ public class WebSecurityConfig { private final CustomOauth2UserService customOauth2UserService; private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; private final CustomOAuth2FailureHandler customOAuth2FailureHandler; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final TestAuthenticationFilter testAuthenticationFilter; private void defaultBasicFilterChain(HttpSecurity http) throws Exception { @@ -72,7 +74,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ) .exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(customAuthenticationEntryPoint()) + .authenticationEntryPoint(customAuthenticationEntryPoint.oAuth2EntryPoint()) ) .addFilterBefore(testAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // .addFilterBefore(jwtAuthenticaltionFilter, UsernamePasswordAuthenticationFilter.class); @@ -80,15 +82,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti return http.build(); } - @Bean - public AuthenticationEntryPoint customAuthenticationEntryPoint() { - return (request, response, authException) -> { - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"로그인이 필요합니다.\"}"); - }; - } - @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); diff --git a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomAuthenticationEntryPoint.java b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..5647491 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomAuthenticationEntryPoint.java @@ -0,0 +1,40 @@ +package org.mainapplication.global.oauth2.handler; + +import org.mainapplication.global.error.ErrorResponse; +import org.mainapplication.global.response.GlobalResponse; +import org.springframework.http.HttpStatus; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class CustomAuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + public AuthenticationEntryPoint oAuth2EntryPoint() { + return (request, response, authException) -> { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ErrorResponse errorResponse = ErrorResponse.of( + authException.getClass().getSimpleName(), + "로그인이 필요합니다." + ); + + GlobalResponse globalResponse = GlobalResponse.fail( + HttpStatus.UNAUTHORIZED.value(), + errorResponse + ); + + // JSON 변환 후 응답 + String jsonResponse = objectMapper.writeValueAsString(globalResponse); + response.getWriter().write(jsonResponse); + }; + } +} \ No newline at end of file diff --git a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java index 5819a4f..bb37b82 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java +++ b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java @@ -2,10 +2,16 @@ import java.io.IOException; +import org.mainapplication.global.constants.UrlConstants; +import org.mainapplication.global.error.ErrorResponse; +import org.mainapplication.global.response.GlobalResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -14,11 +20,9 @@ public class CustomOAuth2FailureHandler implements AuthenticationFailureHandler @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws - IOException { + IOException, ServletException { - // 실패 응답을 JSON으로 반환 - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"error\": \"Authentication failed\", \"message\": \"" + exception.getMessage() + "\"}"); + //TODO 서비스 페이지로 리다이렉트할지, Error Response를 리턴할지 추후에 결정 + response.sendRedirect(UrlConstants.PROD_DOMAIN_URL + "?error=" + exception.getMessage()); } } \ No newline at end of file From 22e1dd1ba6a1205e4d8df31bda14664cdf55acfb Mon Sep 17 00:00:00 2001 From: rong5026 Date: Wed, 22 Jan 2025 11:49:39 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=EC=A0=84=EC=97=AD=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EC=B2=98=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/error/ErrorResponse.java | 8 + .../global/error/GlobalExceptionHandler.java | 240 ++++++++++++++++++ .../error/exception/CustomException.java | 16 ++ .../global/error/exception/ErrorCode.java | 22 ++ .../error/exception/ErrorCodeStatus.java | 11 + .../health/HealCheckController.java | 5 +- 6 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 application/main-application/src/main/java/org/mainapplication/global/error/ErrorResponse.java create mode 100644 application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java create mode 100644 application/main-application/src/main/java/org/mainapplication/global/error/exception/CustomException.java create mode 100644 application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCode.java create mode 100644 application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCodeStatus.java diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/ErrorResponse.java b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorResponse.java new file mode 100644 index 0000000..37e15a6 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorResponse.java @@ -0,0 +1,8 @@ +package org.mainapplication.global.error; + +public record ErrorResponse(String errorClassName, String message) { + + public static ErrorResponse of(String errorClassName, String message) { + return new ErrorResponse(errorClassName, message); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java b/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java new file mode 100644 index 0000000..9c18b16 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java @@ -0,0 +1,240 @@ +package org.mainapplication.global.error; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mainapplication.global.error.exception.CustomException; +import org.mainapplication.global.error.exception.ErrorCode; +import org.mainapplication.global.error.exception.ErrorCodeStatus; +import org.mainapplication.global.response.GlobalResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @Override + protected ResponseEntity handleExceptionInternal( + Exception ex, + Object body, + HttpHeaders headers, + HttpStatusCode statusCode, + WebRequest request) { + + ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), ex.getMessage()); + return super.handleExceptionInternal(ex, errorResponse, headers, statusCode, request); + } + + /** + * enum 타입 String의 값을 잘못입력 햇을 때 + */ + @Override + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("HttpMessageNotReadableException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = ErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * 메소드 인자가 유효하지 않을 때 클라이언트에게 HTTP 400 상태 코드(Bad Request)를 반환. + * 주로 Valid, @valiated 에러시 발생 + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("MethodArgumentNotValidException : {}", ex.getMessage(), ex); + String errorMessage = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorMessage); + final GlobalResponse response = GlobalResponse.fail(status.value(), errorResponse); + return ResponseEntity.status(status).body(response); + } + + /** + * 지원되지 않는 HTTP 요청 메서드를 사용했을 때 클라이언트에게 HTTP 405 상태 코드(Method Not Allowed)를 반환 + */ + @Override + protected ResponseEntity handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("HttpRequestMethodNotSupportedException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = ErrorCode.METHOD_NOT_ALLOWED; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * 요청 주소가 없는 주소일 경우 404 상태코드(Not Found) + */ + @Override + protected ResponseEntity handleNoHandlerFoundException( + NoHandlerFoundException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("NoHandlerFoundException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = ErrorCode.NOT_FOUND_REQUEST_ADDRESS; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * 요청 리소스가 없을 경우 (없는 주소로 요청) + */ + @Override + protected ResponseEntity handleNoResourceFoundException( + NoResourceFoundException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("NoResourceFoundException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = ErrorCode.NOT_FOUND_REQUEST_RESOURCE; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * @RequestParam의 값이 없을 때 + */ + @Override + protected ResponseEntity handleMissingServletRequestParameter( + MissingServletRequestParameterException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("MissingServletRequestParameterException : {}", ex.getMessage(), ex); + + final ErrorCodeStatus errorCodeStatus = ErrorCode.REQUESTED_PARAM_NOT_VALIDATE; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * @Valid나 @Validated 애노테이션을 사용하여 요청 본문 또는 메서드의 인자에 대한 유효성을 검사할 때 발생 + * List의 request를 받을 때 발생 + */ + @Override + protected ResponseEntity handleHandlerMethodValidationException( + HandlerMethodValidationException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("HandlerMethodValidationException : {}", ex.getMessage(), ex); + + final ErrorCodeStatus errorCodeStatus = ErrorCode.REQUESTED_VALUE_NOT_VALIDATE; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** enum type 일치하지 않아 binding 못할 경우 발생 주로 @RequestParam enum으로 binding 못했을 경우 발생 */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + protected ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException ex) { + + log.error("MethodArgumentTypeMismatchException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = ErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** CustomException 예외 처리 */ + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException ex) { + + log.error("CustomException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = ex.getErrorCodeStatus(); + final ErrorResponse errorResponse = ErrorResponse.of(errorCodeStatus.name(), errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** 500번대 에러 처리 */ + @ExceptionHandler(Exception.class) + protected ResponseEntity handleException(Exception ex) { + + log.error("Internal Server Error : {}", ex.getMessage(), ex); + final ErrorCodeStatus internalServerError = ErrorCode.INTERNAL_SERVER_ERROR; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + internalServerError.getMessage()); + final GlobalResponse response = GlobalResponse.fail(internalServerError.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(internalServerError.getHttpStatus()).body(response); + } + + /** Request Param Validation 예외 처리 */ + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException( + ConstraintViolationException e) { + log.error("ConstraintViolationException : {}", e.getMessage(), e); + + Map bindingErrors = new HashMap<>(); + e.getConstraintViolations() + .forEach( + constraintViolation -> { + List propertyPath = + List.of( + constraintViolation + .getPropertyPath() + .toString() + .split("\\.")); + String path = + propertyPath.stream() + .skip(propertyPath.size() - 1L) + .findFirst() + .orElse(null); + bindingErrors.put(path, constraintViolation.getMessage()); + }); + + final ErrorResponse errorResponse = ErrorResponse.of(e.getClass().getSimpleName(), bindingErrors.toString()); + final GlobalResponse response = GlobalResponse.fail(HttpStatus.BAD_REQUEST.value(), errorResponse); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/CustomException.java b/application/main-application/src/main/java/org/mainapplication/global/error/exception/CustomException.java new file mode 100644 index 0000000..ba5923d --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/exception/CustomException.java @@ -0,0 +1,16 @@ +package org.mainapplication.global.error.exception; + +import java.io.Serializable; + +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException implements Serializable { + + private final transient ErrorCodeStatus errorCodeStatus; + + public CustomException(ErrorCodeStatus errorCodeStatus) { + super(errorCodeStatus.getMessage()); + this.errorCodeStatus = errorCodeStatus; + } +} \ No newline at end of file diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCode.java b/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCode.java new file mode 100644 index 0000000..4c23be8 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCode.java @@ -0,0 +1,22 @@ +package org.mainapplication.global.error.exception; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ErrorCode implements ErrorCodeStatus { + // 서버 오류 + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 HTTP method 입니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류, 관리자에게 문의하세요"), + METHOD_ARGUMENT_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "Enum Type이 일치하지 않거나 값이 없습니다."), + REQUESTED_PARAM_NOT_VALIDATE(HttpStatus.BAD_REQUEST, "Reqeust Param 값이 유효하지 않습니다"), + REQUESTED_VALUE_NOT_VALIDATE(HttpStatus.BAD_REQUEST, "메서드의 인자가 유효하지 않습니다."), + NOT_FOUND_REQUEST_ADDRESS(HttpStatus.NOT_FOUND, "잘못된 요청 주소입니다."), + NOT_FOUND_REQUEST_RESOURCE(HttpStatus.NOT_FOUND, "존재하지 않은 요청 주소입니다."); + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCodeStatus.java b/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCodeStatus.java new file mode 100644 index 0000000..739e0e7 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCodeStatus.java @@ -0,0 +1,11 @@ +package org.mainapplication.global.error.exception; + +import org.springframework.http.HttpStatus; + +public interface ErrorCodeStatus { + String name(); + + HttpStatus getHttpStatus(); + + String getMessage(); +} diff --git a/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java b/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java index bc2bc71..e9583dc 100644 --- a/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java +++ b/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java @@ -1,5 +1,6 @@ package org.mainapplication.health; +import org.mainapplication.global.response.GlobalResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,7 +14,7 @@ public class HealCheckController { @Operation(summary = "서버 상태 체크", security = {}) @GetMapping("/health") - public ResponseEntity healthCheck() { - return ResponseEntity.ok("OK"); + public ResponseEntity healthCheck() { + return ResponseEntity.ok(GlobalResponse.success(200, "OK")); } } From 25fb257b596adb2e11c2bea8318ac07f44a4a5e4 Mon Sep 17 00:00:00 2001 From: rong5026 Date: Tue, 28 Jan 2025 16:34:40 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20=EC=A0=84=EC=97=AD=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EA=B0=92=20Success:=20boolean=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthController.java | 1 - .../org/mainapplication/global/response/GlobalResponse.java | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java b/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java index 6b14c39..c1b8dec 100644 --- a/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java +++ b/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java @@ -25,5 +25,4 @@ public ResponseEntity refreshAccessToken(@CookieValue("RefreshToken") St String newAccessToken = tokenService.reissueAccessToken(refreshToken, response); return ResponseEntity.ok(newAccessToken); } - } diff --git a/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java index fa0e869..bc260b5 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java +++ b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java @@ -4,12 +4,12 @@ import org.mainapplication.global.error.ErrorResponse; -public record GlobalResponse(boolean success, int status, Object data, LocalDateTime timestamp) { +public record GlobalResponse(int status, Object data, LocalDateTime timestamp) { public static GlobalResponse success(int status, Object data) { - return new GlobalResponse(true, status, data, LocalDateTime.now()); + return new GlobalResponse(status, data, LocalDateTime.now()); } public static GlobalResponse fail(int status, ErrorResponse errorResponse) { - return new GlobalResponse(false, status, errorResponse, LocalDateTime.now()); + return new GlobalResponse(status, errorResponse, LocalDateTime.now()); } } From 70cd86b014d1392de2472c54b86e773e9fb4fc81 Mon Sep 17 00:00:00 2001 From: rong5026 Date: Tue, 28 Jan 2025 16:49:26 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=8A=A5=EB=B3=84=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{exception => }/CustomException.java | 6 +++--- .../{exception => }/ErrorCodeStatus.java | 2 +- .../global/error/GlobalExceptionHandler.java | 20 +++++++++---------- .../CommonErrorCode.java} | 5 +++-- .../error/exception/CommonException.java | 10 ++++++++++ .../handler/CustomOAuth2FailureHandler.java | 4 ---- 6 files changed, 26 insertions(+), 21 deletions(-) rename application/main-application/src/main/java/org/mainapplication/global/error/{exception => }/CustomException.java (51%) rename application/main-application/src/main/java/org/mainapplication/global/error/{exception => }/ErrorCodeStatus.java (74%) rename application/main-application/src/main/java/org/mainapplication/global/error/{exception/ErrorCode.java => code/CommonErrorCode.java} (85%) create mode 100644 application/main-application/src/main/java/org/mainapplication/global/error/exception/CommonException.java diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/CustomException.java b/application/main-application/src/main/java/org/mainapplication/global/error/CustomException.java similarity index 51% rename from application/main-application/src/main/java/org/mainapplication/global/error/exception/CustomException.java rename to application/main-application/src/main/java/org/mainapplication/global/error/CustomException.java index ba5923d..ea978a0 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/error/exception/CustomException.java +++ b/application/main-application/src/main/java/org/mainapplication/global/error/CustomException.java @@ -1,15 +1,15 @@ -package org.mainapplication.global.error.exception; +package org.mainapplication.global.error; import java.io.Serializable; import lombok.Getter; @Getter -public class CustomException extends RuntimeException implements Serializable { +public abstract class CustomException extends RuntimeException implements Serializable { private final transient ErrorCodeStatus errorCodeStatus; - public CustomException(ErrorCodeStatus errorCodeStatus) { + protected CustomException(ErrorCodeStatus errorCodeStatus) { super(errorCodeStatus.getMessage()); this.errorCodeStatus = errorCodeStatus; } diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCodeStatus.java b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorCodeStatus.java similarity index 74% rename from application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCodeStatus.java rename to application/main-application/src/main/java/org/mainapplication/global/error/ErrorCodeStatus.java index 739e0e7..e32fccc 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCodeStatus.java +++ b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorCodeStatus.java @@ -1,4 +1,4 @@ -package org.mainapplication.global.error.exception; +package org.mainapplication.global.error; import org.springframework.http.HttpStatus; diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java b/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java index 9c18b16..a71dd6e 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java +++ b/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java @@ -4,9 +4,7 @@ import java.util.List; import java.util.Map; -import org.mainapplication.global.error.exception.CustomException; -import org.mainapplication.global.error.exception.ErrorCode; -import org.mainapplication.global.error.exception.ErrorCodeStatus; +import org.mainapplication.global.error.code.CommonErrorCode; import org.mainapplication.global.response.GlobalResponse; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -55,7 +53,7 @@ protected ResponseEntity handleHttpMessageNotReadable( WebRequest request) { log.error("HttpMessageNotReadableException : {}", ex.getMessage(), ex); - final ErrorCodeStatus errorCodeStatus = ErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorCodeStatus.getMessage()); final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); @@ -91,7 +89,7 @@ protected ResponseEntity handleHttpRequestMethodNotSupported( WebRequest request) { log.error("HttpRequestMethodNotSupportedException : {}", ex.getMessage(), ex); - final ErrorCodeStatus errorCodeStatus = ErrorCode.METHOD_NOT_ALLOWED; + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.METHOD_NOT_ALLOWED; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorCodeStatus.getMessage()); final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); @@ -109,7 +107,7 @@ protected ResponseEntity handleNoHandlerFoundException( WebRequest request) { log.error("NoHandlerFoundException : {}", ex.getMessage(), ex); - final ErrorCodeStatus errorCodeStatus = ErrorCode.NOT_FOUND_REQUEST_ADDRESS; + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.NOT_FOUND_REQUEST_ADDRESS; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorCodeStatus.getMessage()); final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); @@ -127,7 +125,7 @@ protected ResponseEntity handleNoResourceFoundException( WebRequest request) { log.error("NoResourceFoundException : {}", ex.getMessage(), ex); - final ErrorCodeStatus errorCodeStatus = ErrorCode.NOT_FOUND_REQUEST_RESOURCE; + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.NOT_FOUND_REQUEST_RESOURCE; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorCodeStatus.getMessage()); final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); @@ -146,7 +144,7 @@ protected ResponseEntity handleMissingServletRequestParameter( log.error("MissingServletRequestParameterException : {}", ex.getMessage(), ex); - final ErrorCodeStatus errorCodeStatus = ErrorCode.REQUESTED_PARAM_NOT_VALIDATE; + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.REQUESTED_PARAM_NOT_VALIDATE; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorCodeStatus.getMessage()); final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); @@ -166,7 +164,7 @@ protected ResponseEntity handleHandlerMethodValidationException( log.error("HandlerMethodValidationException : {}", ex.getMessage(), ex); - final ErrorCodeStatus errorCodeStatus = ErrorCode.REQUESTED_VALUE_NOT_VALIDATE; + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.REQUESTED_VALUE_NOT_VALIDATE; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorCodeStatus.getMessage()); final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); @@ -179,7 +177,7 @@ protected ResponseEntity handleMethodArgumentTypeMismatchExcepti MethodArgumentTypeMismatchException ex) { log.error("MethodArgumentTypeMismatchException : {}", ex.getMessage(), ex); - final ErrorCodeStatus errorCodeStatus = ErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorCodeStatus.getMessage()); final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); @@ -202,7 +200,7 @@ public ResponseEntity handleCustomException(CustomException ex) protected ResponseEntity handleException(Exception ex) { log.error("Internal Server Error : {}", ex.getMessage(), ex); - final ErrorCodeStatus internalServerError = ErrorCode.INTERNAL_SERVER_ERROR; + final ErrorCodeStatus internalServerError = CommonErrorCode.INTERNAL_SERVER_ERROR; final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), internalServerError.getMessage()); final GlobalResponse response = GlobalResponse.fail(internalServerError.getHttpStatus().value(), errorResponse); diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCode.java b/application/main-application/src/main/java/org/mainapplication/global/error/code/CommonErrorCode.java similarity index 85% rename from application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCode.java rename to application/main-application/src/main/java/org/mainapplication/global/error/code/CommonErrorCode.java index 4c23be8..4141b36 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/error/exception/ErrorCode.java +++ b/application/main-application/src/main/java/org/mainapplication/global/error/code/CommonErrorCode.java @@ -1,5 +1,6 @@ -package org.mainapplication.global.error.exception; +package org.mainapplication.global.error.code; +import org.mainapplication.global.error.ErrorCodeStatus; import org.springframework.http.HttpStatus; import lombok.Getter; @@ -7,7 +8,7 @@ @Getter @RequiredArgsConstructor -public enum ErrorCode implements ErrorCodeStatus { +public enum CommonErrorCode implements ErrorCodeStatus { // 서버 오류 METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 HTTP method 입니다."), INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류, 관리자에게 문의하세요"), diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/CommonException.java b/application/main-application/src/main/java/org/mainapplication/global/error/exception/CommonException.java new file mode 100644 index 0000000..35a3fb1 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/exception/CommonException.java @@ -0,0 +1,10 @@ +package org.mainapplication.global.error.exception; + +import org.mainapplication.global.error.CustomException; +import org.mainapplication.global.error.ErrorCodeStatus; + +public class CommonException extends CustomException { + public CommonException(ErrorCodeStatus errorCodeStatus) { + super(errorCodeStatus); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java index bb37b82..745aa77 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java +++ b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java @@ -3,14 +3,10 @@ import java.io.IOException; import org.mainapplication.global.constants.UrlConstants; -import org.mainapplication.global.error.ErrorResponse; -import org.mainapplication.global.response.GlobalResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; -import com.fasterxml.jackson.databind.ObjectMapper; - import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse;