[Feat] Swagger / 공통 응답 / 예외 처리 / Security / CORS 초기 설정 - #2
Conversation
- BaseEntity: createdAt, updatedAt JPA Auditing 적용 - ApiResponse: success/created 공통 응답 래퍼 - ErrorResponse: 에러 코드, 메시지, 경로 포함 에러 응답 - ResponseUtils: ResponseEntity 생성 유틸 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ErrorCode: HTTP 상태 코드별 공통/유저 에러 코드 정의 - DocGridException: 비즈니스 예외 클래스 - GlobalExceptionHandler: 전역 예외 핸들러 (Validation, DB, 인증 등) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SecurityConfig: CSRF 비활성화, CORS 연동, 엔드포인트 인가 설정 - CorsConfig: 허용 오리진/메서드/헤더 설정 - DocgridApplication: @EnableJpaAuditing 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- springdoc-openapi 2.8.9 의존성 추가 - SwaggerConfig: JWT Bearer 인증, 프로필별 서버 URL, 인증 유지 설정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- local 프로필에서만 활성화되는 /test/** 엔드포인트 - 공통 응답/예외 처리 동작 검증용 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthrough
Changes공통 인프라 구성
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/main/java/com/opensource/docgrid/global/config/SwaggerConfig.java (1)
59-63: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winSwagger UI의 토큰 영속 저장은 기본값으로 두지 않는 편이 안전합니다.
Line 62의
persistAuthorization(true)는 브라우저에 Bearer 토큰을 남겨 새로고침 이후에도 재사용되게 합니다. 운영에서 Swagger를 열어둘 계획이라면 공유 단말이나 공용 브라우저 세션에서 토큰 노출 범위가 커지므로, 기본은false로 두고 필요할 때만 프로필별로 켜는 편이 안전합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/opensource/docgrid/global/config/SwaggerConfig.java` around lines 59 - 63, The Swagger UI configuration in SwaggerConfig.swaggerUiConfigProperties currently forces token persistence on by calling setPersistAuthorization(true), which should not be the default. Change this bean to leave persistAuthorization disabled by default, and if persistence is needed, make it profile- or environment-specific so it is only enabled intentionally for SwaggerUiConfigProperties setup.src/main/java/com/opensource/docgrid/global/config/SecurityConfig.java (1)
25-27: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
/test/**공개 규칙은 로컬 프로필과 함께 묶는 편이 안전합니다.Line 26의
permitAll()은src/main/java/com/opensource/docgrid/global/common/TestController.java가@Profile("local")로 제한돼 있어도 모든 프로필에서 계속 남습니다. 지금은 로컬 전용 컨트롤러뿐이지만, 이후/test/**경로가 추가되면 운영에서도 무인증으로 열리니 프로필 조건부 설정으로 분리해 두는 편이 안전합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/opensource/docgrid/global/config/SecurityConfig.java` around lines 25 - 27, The SecurityConfig authorization rule currently exposes /test/** in all profiles, even though TestController is local-only; update the configuration so the /test/** permitAll() mapping is applied only when the local profile is active. Keep the existing authorizeHttpRequests setup in SecurityConfig, but gate the /test/** matcher behind a profile-specific condition or separate local-only security configuration so future test endpoints are not unintentionally public in non-local environments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/opensource/docgrid/global/common/response/ApiResponse.java`:
- Around line 20-21: `ApiResponse`와 `ErrorResponse`의 `timestamp`는
`LocalDateTime.now()`와 `@JsonFormat`의 timezone 조합으로 KST가 보장되지 않으니, 시간대가 필요한 응답
타입에 맞게 `OffsetDateTime`, `ZonedDateTime`, 또는 `Instant`로 변경하고 생성/직렬화 기준을
`Asia/Seoul`에 맞춰 통일하세요. 특히 `timestamp` 필드와 응답 생성 로직을 함께 수정해 서버 기본 시간대에 의존하지 않도록
정리하세요.
In
`@src/main/java/com/opensource/docgrid/global/common/response/ResponseUtils.java`:
- Around line 24-25: `ResponseUtils.noContent()`의 반환 타입이 실제 응답과 맞지 않아 바디 없는 204
응답이 `ApiResponse<T>`를 가진 것처럼 보입니다. `noContent()`는 `ResponseEntity<Void>`를 반환하도록
바꾸고, 이 메서드를 사용하는 컨트롤러 반환 계약도 함께 맞춰서 바디 없는 응답이 명확히 드러나게 정리하세요. `ResponseUtils`의
`noContent` 시그니처를 기준으로 관련 문서/스키마 추론이 `ApiResponse`를 잡지 않도록 분리하면 됩니다.
In `@src/main/java/com/opensource/docgrid/global/common/TestController.java`:
- Around line 40-46: The created endpoint in TestController is using GET for a
201 Created response, which conflicts with HTTP semantics. Update the created()
handler to use POST if it is meant to represent resource creation, or change the
response to a 200-series non-created example if it is only a sample. Keep the
existing ResponseUtils.created flow only if the method is renamed and mapped
through a POST handler.
In
`@src/main/java/com/opensource/docgrid/global/exception/GlobalExceptionHandler.java`:
- Around line 56-84: The validation handlers in GlobalExceptionHandler currently
log the specific failure but return only the generic INVALID_PARAMETER body, so
the client loses actionable input details. Update handleConstraintViolation,
handleMissingParam, and handleTypeMismatch to build a short detail message from
each exception (similar to MethodArgumentNotValidException) and pass it through
ErrorResponse.of(ErrorCode.INVALID_PARAMETER, detail, request) instead of the
request-only overload. Keep the existing logging, but make the response body
reflect the actual invalid field/parameter information using the
exception-specific values from ConstraintViolationException,
MissingServletRequestParameterException, and
MethodArgumentTypeMismatchException.
- Around line 116-124: Move the 401 authentication-failure handling out of
GlobalExceptionHandler and into an AuthenticationEntryPoint, because
`@ExceptionHandler`(AuthenticationException.class) in the RestControllerAdvice
will not catch exceptions thrown from the filter chain. Keep the same
ErrorResponse format there by centralizing the unauthorized response logic in
the entry point, and use the existing AuthenticationException/UNAUTHORIZED
handling only if it is raised inside controller flow.
---
Nitpick comments:
In `@src/main/java/com/opensource/docgrid/global/config/SecurityConfig.java`:
- Around line 25-27: The SecurityConfig authorization rule currently exposes
/test/** in all profiles, even though TestController is local-only; update the
configuration so the /test/** permitAll() mapping is applied only when the local
profile is active. Keep the existing authorizeHttpRequests setup in
SecurityConfig, but gate the /test/** matcher behind a profile-specific
condition or separate local-only security configuration so future test endpoints
are not unintentionally public in non-local environments.
In `@src/main/java/com/opensource/docgrid/global/config/SwaggerConfig.java`:
- Around line 59-63: The Swagger UI configuration in
SwaggerConfig.swaggerUiConfigProperties currently forces token persistence on by
calling setPersistAuthorization(true), which should not be the default. Change
this bean to leave persistAuthorization disabled by default, and if persistence
is needed, make it profile- or environment-specific so it is only enabled
intentionally for SwaggerUiConfigProperties setup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 5cb9d773-5e82-493a-b0a5-0e64ed762f77
📒 Files selected for processing (13)
build.gradlesrc/main/java/com/opensource/docgrid/DocgridApplication.javasrc/main/java/com/opensource/docgrid/global/common/TestController.javasrc/main/java/com/opensource/docgrid/global/common/entity/BaseEntity.javasrc/main/java/com/opensource/docgrid/global/common/response/ApiResponse.javasrc/main/java/com/opensource/docgrid/global/common/response/ErrorResponse.javasrc/main/java/com/opensource/docgrid/global/common/response/ResponseUtils.javasrc/main/java/com/opensource/docgrid/global/config/CorsConfig.javasrc/main/java/com/opensource/docgrid/global/config/SecurityConfig.javasrc/main/java/com/opensource/docgrid/global/config/SwaggerConfig.javasrc/main/java/com/opensource/docgrid/global/exception/DocGridException.javasrc/main/java/com/opensource/docgrid/global/exception/ErrorCode.javasrc/main/java/com/opensource/docgrid/global/exception/GlobalExceptionHandler.java
| @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") | ||
| private final LocalDateTime timestamp = LocalDateTime.now(); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Jackson에서 @jsonformat(timezone=...)이 LocalDateTime 직렬화 시 실제 시간대 변환을 적용하는지 확인해 주세요.
💡 Result:
Jackson에서 LocalDateTime을 직렬화할 때 @JsonFormat(timezone=...)을 사용하는 경우, LocalDateTime은 본질적으로 시간대 정보(Time Zone)를 포함하지 않는 타입이기 때문에 timezone 설정만으로는 의도한 시간대 변환이 자동으로 이루어지지 않습니다 [1][2]. LocalDateTime은 단순히 날짜와 시간 정보만을 나타내며, 특정 시간대에 고정되어 있지 않습니다. 따라서 Jackson은 이를 직렬화할 때 시간대 변환을 적용할 기준 시점(Offset)이 없어 해당 설정을 무시하거나 시간대 보정 없이 ISO-8601 형식으로 출력하게 됩니다 [1][2]. 참고해야 할 핵심 사항은 다음과 같습니다: 1. 시간대 변환의 필요성: 만약 특정 시간대(예: UTC)를 기준으로 변환이 필요하다면, 직렬화하려는 데이터를 LocalDateTime이 아닌 ZonedDateTime이나 OffsetDateTime처럼 시간대 정보가 포함된 타입으로 변경하여 사용하는 것이 적절합니다 [2][3]. 2. @JsonFormat의 한계: LocalDateTime에 @JsonFormat(timezone = "...")을 설정하는 것은 기술적으로 가능하지만, LocalDateTime 타입 자체가 시간대 오프셋 정보를 담고 있지 않으므로 Jackson의 직렬화 과정에서 시간대 계산 로직이 적용되지 않습니다 [2][3]. 3. 권장 접근 방식: - 시간대 변환이 중요한 경우: 데이터를 ZonedDateTime으로 변환한 뒤 @JsonFormat(timezone = "...")을 사용하십시오 [3]. - 단순히 형식만 변경하려는 경우: @JsonFormat(pattern = "...") 속성을 사용하여 원하는 출력 포맷을 지정하십시오 [4][3]. 요약하자면, LocalDateTime은 시간대가 없는 객체이므로 @JsonFormat의 timezone 속성을 적용해도 시간대 변환 효과는 발생하지 않습니다 [2][3].
Citations:
- 1: https://javadoc.io/static/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.17.3/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.html
- 2: https://github.com/FasterXML/jackson-modules-java8/tree/2.14/datetime
- 3: https://stackoverflow.com/questions/62960045/inconsistent-behaviour-of-jacksons-jsonformat-for-old-date-api-and-zoneddatetim
- 4: https://www.baeldung.com/spring-boot-formatting-json-dates
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File list ==\n'
git ls-files 'src/main/java/com/opensource/docgrid/global/common/response/*.java'
printf '\n== Outline ApiResponse.java ==\n'
ast-grep outline src/main/java/com/opensource/docgrid/global/common/response/ApiResponse.java --view expanded || true
printf '\n== Outline ErrorResponse.java ==\n'
ast-grep outline src/main/java/com/opensource/docgrid/global/common/response/ErrorResponse.java --view expanded || true
printf '\n== Line counts ==\n'
wc -l src/main/java/com/opensource/docgrid/global/common/response/ApiResponse.java \
src/main/java/com/opensource/docgrid/global/common/response/ErrorResponse.java
printf '\n== Relevant snippets ==\n'
sed -n '1,120p' src/main/java/com/opensource/docgrid/global/common/response/ApiResponse.java
printf '\n---\n'
sed -n '1,120p' src/main/java/com/opensource/docgrid/global/common/response/ErrorResponse.java
printf '\n== Jackson config search ==\n'
rg -n "ObjectMapper|JavaTimeModule|`@JsonFormat`|timezone|LocalDateTime|OffsetDateTime|ZonedDateTime" src/main/java || trueRepository: DocGrid/backend
Length of output: 5427
LocalDateTime에 timezone를 붙여도 KST 보장이 안 됩니다. ApiResponse.java와 ErrorResponse.java의 timestamp는 LocalDateTime.now()라 서버 기본 시간대에 따라 값이 달라지고, 직렬화 시점에 Asia/Seoul 변환도 기대하기 어렵습니다. 시간대가 필요한 응답이면 OffsetDateTime/ZonedDateTime/Instant로 맞춰 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/opensource/docgrid/global/common/response/ApiResponse.java`
around lines 20 - 21, `ApiResponse`와 `ErrorResponse`의 `timestamp`는
`LocalDateTime.now()`와 `@JsonFormat`의 timezone 조합으로 KST가 보장되지 않으니, 시간대가 필요한 응답
타입에 맞게 `OffsetDateTime`, `ZonedDateTime`, 또는 `Instant`로 변경하고 생성/직렬화 기준을
`Asia/Seoul`에 맞춰 통일하세요. 특히 `timestamp` 필드와 응답 생성 로직을 함께 수정해 서버 기본 시간대에 의존하지 않도록
정리하세요.
| public <T> ResponseEntity<ApiResponse<T>> noContent() { | ||
| return ResponseEntity.noContent().build(); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
204 No Content 시그니처가 실제 응답 계약과 다릅니다.
이 메서드는 바디가 없는 응답을 반환하면서 타입은 ResponseEntity<ApiResponse<T>>로 선언합니다. 그러면 컨트롤러 반환 타입과 문서 스키마가 바디가 있는 것처럼 굳어질 수 있어서 ResponseEntity<Void>로 분리하는 편이 안전합니다.
예시 수정
- public <T> ResponseEntity<ApiResponse<T>> noContent() {
+ public ResponseEntity<Void> noContent() {
return ResponseEntity.noContent().build();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public <T> ResponseEntity<ApiResponse<T>> noContent() { | |
| return ResponseEntity.noContent().build(); | |
| public ResponseEntity<Void> noContent() { | |
| return ResponseEntity.noContent().build(); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/opensource/docgrid/global/common/response/ResponseUtils.java`
around lines 24 - 25, `ResponseUtils.noContent()`의 반환 타입이 실제 응답과 맞지 않아 바디 없는 204
응답이 `ApiResponse<T>`를 가진 것처럼 보입니다. `noContent()`는 `ResponseEntity<Void>`를 반환하도록
바꾸고, 이 메서드를 사용하는 컨트롤러 반환 계약도 함께 맞춰서 바디 없는 응답이 명확히 드러나게 정리하세요. `ResponseUtils`의
`noContent` 시그니처를 기준으로 관련 문서/스키마 추론이 `ApiResponse`를 잡지 않도록 분리하면 됩니다.
| @GetMapping("/created") | ||
| public ResponseEntity<ApiResponse<SampleData>> created() { | ||
| SampleData data = SampleData.builder() | ||
| .id("docgrid-new") | ||
| .message("리소스 생성") | ||
| .build(); | ||
| return ResponseUtils.created(data); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
201 Created 검증 엔드포인트는 GET보다 POST가 맞습니다.
Line 40의 GET /created는 실제 생성 시나리오 없이 201을 반환해 HTTP 메서드 의미와 어긋납니다. 응답 유틸 테스트 목적이라도 생성 케이스는 POST로 분리하거나, 단순 상태코드 샘플이라면 200 계열 응답으로 이름을 바꾸는 편이 혼선을 줄입니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/opensource/docgrid/global/common/TestController.java`
around lines 40 - 46, The created endpoint in TestController is using GET for a
201 Created response, which conflicts with HTTP semantics. Update the created()
handler to use POST if it is meant to represent resource creation, or change the
response to a 200-series non-created example if it is only a sample. Keep the
existing ResponseUtils.created flow only if the method is renamed and mapped
through a POST handler.
| @ExceptionHandler(ConstraintViolationException.class) | ||
| public ResponseEntity<ErrorResponse> handleConstraintViolation( | ||
| ConstraintViolationException e, HttpServletRequest request | ||
| ) { | ||
| log.warn("[ConstraintViolation] {} {} | {}", request.getMethod(), request.getRequestURI(), e.getMessage()); | ||
| return ResponseEntity | ||
| .status(HttpStatus.BAD_REQUEST) | ||
| .body(ErrorResponse.of(ErrorCode.INVALID_PARAMETER, request)); | ||
| } | ||
|
|
||
| @ExceptionHandler(MissingServletRequestParameterException.class) | ||
| public ResponseEntity<ErrorResponse> handleMissingParam( | ||
| MissingServletRequestParameterException e, HttpServletRequest request | ||
| ) { | ||
| log.warn("[MissingParam] {} {} | {}", request.getMethod(), request.getRequestURI(), e.getMessage()); | ||
| return ResponseEntity | ||
| .status(HttpStatus.BAD_REQUEST) | ||
| .body(ErrorResponse.of(ErrorCode.INVALID_PARAMETER, request)); | ||
| } | ||
|
|
||
| @ExceptionHandler(MethodArgumentTypeMismatchException.class) | ||
| public ResponseEntity<ErrorResponse> handleTypeMismatch( | ||
| MethodArgumentTypeMismatchException e, HttpServletRequest request | ||
| ) { | ||
| log.warn("[TypeMismatch] {} {} | {}", request.getMethod(), request.getRequestURI(), e.getMessage()); | ||
| return ResponseEntity | ||
| .status(HttpStatus.BAD_REQUEST) | ||
| .body(ErrorResponse.of(ErrorCode.INVALID_PARAMETER, request)); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
검증 실패 상세가 응답 본문에서 사라집니다.
여기서는 상세 원인을 로그에만 남기고 본문에는 공통 메시지만 내려서 클라이언트가 어떤 입력을 고쳐야 하는지 알기 어렵습니다. MethodArgumentNotValidException처럼 짧은 detail을 만들어 ErrorResponse.of(..., detail, request)로 내려주는 쪽이 일관됩니다. As per path instructions, src/main/java/**/*.java: 예외 처리에 중점을 둔다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/opensource/docgrid/global/exception/GlobalExceptionHandler.java`
around lines 56 - 84, The validation handlers in GlobalExceptionHandler
currently log the specific failure but return only the generic INVALID_PARAMETER
body, so the client loses actionable input details. Update
handleConstraintViolation, handleMissingParam, and handleTypeMismatch to build a
short detail message from each exception (similar to
MethodArgumentNotValidException) and pass it through
ErrorResponse.of(ErrorCode.INVALID_PARAMETER, detail, request) instead of the
request-only overload. Keep the existing logging, but make the response body
reflect the actual invalid field/parameter information using the
exception-specific values from ConstraintViolationException,
MissingServletRequestParameterException, and
MethodArgumentTypeMismatchException.
Source: Path instructions
| @ExceptionHandler(AuthenticationException.class) | ||
| public ResponseEntity<ErrorResponse> handleAuthentication( | ||
| AuthenticationException e, HttpServletRequest request | ||
| ) { | ||
| log.warn("[Unauthorized] {} {} | {}", request.getMethod(), request.getRequestURI(), e.getMessage()); | ||
| return ResponseEntity | ||
| .status(HttpStatus.UNAUTHORIZED) | ||
| .body(ErrorResponse.of(ErrorCode.UNAUTHORIZED, request)); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Spring Security에서 필터 체인에서 발생한 AuthenticationException을 @RestControllerAdvice가 처리할 수 있는지, 아니면 AuthenticationEntryPoint가 필요한지 확인해 주세요.
💡 Result:
Spring Security 필터 체인에서 발생하는 AuthenticationException은 @RestControllerAdvice로 처리할 수 없습니다. 이는 해당 예외가 DispatcherServlet에 도달하기 전인 필터 레벨에서 발생하고 가로채기 때문입니다 [1][2]. 따라서 필터 체인에서 발생하는 인증 예외를 처리하려면 AuthenticationEntryPoint를 사용해야 합니다 [3][4]. 상세 설명: 1. 동작 원리: Spring Security의 필터 체인에는 ExceptionTranslationFilter가 포함되어 있습니다 [4]. 이 필터는 체인 내에서 발생하는 AuthenticationException 및 AccessDeniedException을 감지합니다 [3][5]. 2. AuthenticationEntryPoint의 역할: ExceptionTranslationFilter가 AuthenticationException을 감지하면, 설정된 AuthenticationEntryPoint의 commence 메서드를 호출하여 인증 실패에 대한 응답(예: 401 Unauthorized 상태 코드 반환, 로그인 페이지로 리다이렉트 등)을 수행합니다 [6][4][7]. 3. @RestControllerAdvice의 한계: @RestControllerAdvice와 @ExceptionHandler는 주로 DispatcherServlet 이후의 계층(컨트롤러 등)에서 발생하는 예외를 처리하도록 설계되었습니다 [1]. 필터 체인에서 던져진 예외는 이 단계까지 전파되지 않으므로 @RestControllerAdvice가 이를 포착할 수 없습니다 [1]. 만약 @RestControllerAdvice를 통해 예외를 일관되게 처리하고 싶다면, AuthenticationEntryPoint 구현체 내부에서 HandlerExceptionResolver를 사용하여 예외를 DispatcherServlet으로 위임하거나 [8], 필터 단계에서 발생한 예외를 직접 응답 객체에 쓰는 방식으로 처리해야 합니다 [6][7].
Citations:
- 1: https://stackoverflow.com/questions/77507609/restcontrolleradvice-not-able-to-catch-basic-authenticationexception-through-cu
- 2: @RestControllerAdvice annotation does not intercept AuthenticationException. spring-projects/spring-security#13302
- 3: https://docs.spring.io/spring-security/site/docs/6.3.9/api/org/springframework/security/web/access/ExceptionTranslationFilter.html
- 4: https://docs.spring.io/spring-security/reference/servlet/architecture.html
- 5: https://docs.spring.io/spring-security/reference/api/java/org/springframework/security/web/access/ExceptionTranslationFilter.html
- 6: https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html
- 7: https://docs.spring.io/spring-security/reference/api/java/org/springframework/security/web/AuthenticationEntryPoint.html
- 8: https://stackoverflow.com/questions/75094463/spring-security-6-handle-exceptions-with-restcontrolleradvice
인증 실패 401 처리는 AuthenticationEntryPoint로 연결해야 합니다.
@RestControllerAdvice의 @ExceptionHandler(AuthenticationException.class)는 필터 체인에서 발생한 예외를 잡지 못하므로, 보호된 엔드포인트의 401 응답이 ErrorResponse로 통일되지 않습니다. 같은 응답 포맷은 AuthenticationEntryPoint에 넣어 처리하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/opensource/docgrid/global/exception/GlobalExceptionHandler.java`
around lines 116 - 124, Move the 401 authentication-failure handling out of
GlobalExceptionHandler and into an AuthenticationEntryPoint, because
`@ExceptionHandler`(AuthenticationException.class) in the RestControllerAdvice
will not catch exceptions thrown from the filter chain. Keep the same
ErrorResponse format there by centralizing the unauthorized response logic in
the entry point, and use the existing AuthenticationException/UNAUTHORIZED
handling only if it is raised inside controller flow.
Source: Path instructions
🔍️ 작업 내용
Closes #(이슈 번호)
feature/1
✨ 상세 설명
BaseEntity 추가 (createdAt, updatedAt JPA Auditing 적용, @EnableJpaAuditing 등록)
공통 응답 포맷 ApiResponse / ErrorResponse / ResponseUtils 구현
ErrorCode Enum 및 DocGridException 커스텀 예외 정의
GlobalExceptionHandler(@RestControllerAdvice)로 Validation, 인증, DB 예외 등 공통 예외 처리
SecurityConfig 기본 필터 체인 설정 (CSRF 비활성화, CORS 연동, /test/** /swagger-ui/** /v3/api-docs/** permitAll)
CorsConfig 설정 (로컬 환경 기준 origin 허용)
SwaggerConfig 설정 (springdoc-openapi 의존성 추가, JWT Bearer 인증 스킴 등록, local/prod 서버 분기)
동작 확인용 TestController 추가 (@Profile("local"))
🛠️ 추후 리팩토링 및 고도화 계획
CORS 허용 origin에 실제 배포 도메인 추가
Swagger prod 서버 URL을 실제 배포 도메인으로 교체
ErrorCode에 도메인별(User, Document, Search 등) 에러코드 추가
Security 인증/인가 로직(JWT 적용) 구현 후 permitAll 범위 재조정
📸 스크린샷 (선택)
💬 리뷰 요구사항
Summary by CodeRabbit
success/status/data/timestamp를 포함한 공통 구조로 통일되었습니다.status/code/message/method/path/timestamp를 포함하는 표준 형식으로 제공됩니다.