Skip to content

[Feat] Swagger / 공통 응답 / 예외 처리 / Security / CORS 초기 설정 - #2

Merged
kangcheolung merged 5 commits into
developfrom
feature/1
Jun 30, 2026
Merged

[Feat] Swagger / 공통 응답 / 예외 처리 / Security / CORS 초기 설정#2
kangcheolung merged 5 commits into
developfrom
feature/1

Conversation

@kangcheolung

@kangcheolung kangcheolung commented Jun 30, 2026

Copy link
Copy Markdown
Member

🔍️ 작업 내용

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

  • New Features
    • API 응답 형식이 success/status/data/timestamp를 포함한 공통 구조로 통일되었습니다.
    • 오류 응답도 status/code/message/method/path/timestamp를 포함하는 표준 형식으로 제공됩니다.
    • Swagger UI와 OpenAPI 문서가 추가되어 API 확인이 쉬워졌습니다.
    • 로컬 환경에서 테스트용 엔드포인트를 통해 정상/예외 응답을 바로 확인할 수 있습니다.
  • Bug Fixes
    • 예외, 요청 검증, 인증 실패, 무결성 오류에 대한 응답이 일관되게 처리됩니다.
    • CORS 및 보안 설정이 정리되어 로컬 프런트엔드 연동이 원활해졌습니다.

kangcheolung and others added 5 commits June 30, 2026 18:23
- 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>
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

ApiResponse/ErrorResponse/ResponseUtils 공통 응답 래퍼, ErrorCode·DocGridException·GlobalExceptionHandler 전역 예외 처리 체계, JPA Auditing용 BaseEntity, CORS·Security·Swagger 설정 클래스, 그리고 로컬 전용 TestController를 일괄 추가했습니다.

Changes

공통 인프라 구성

Layer / File(s) Summary
응답 DTO 및 유틸리티
global/common/response/ApiResponse.java, global/common/response/ErrorResponse.java, global/common/response/ResponseUtils.java
ApiResponse<T>(성공 래퍼), ErrorResponse(에러 래퍼), ResponseUtils(정적 헬퍼)를 추가해 성공/에러 응답 포맷을 표준화한다. timestampAsia/Seoul 기준 포맷, dataNON_NULL 직렬화 정책이 적용된다.
ErrorCode, DocGridException, GlobalExceptionHandler
global/exception/ErrorCode.java, global/exception/DocGridException.java, global/exception/GlobalExceptionHandler.java
ErrorCode enum으로 HTTP 상태·코드·메시지를 정의하고, DocGridException으로 도메인 예외를 구성한다. GlobalExceptionHandler는 검증·인증·데이터·서버 예외 총 12가지를 매핑해 ErrorResponse를 반환한다.
JPA Auditing 활성화 및 BaseEntity
DocgridApplication.java, global/common/entity/BaseEntity.java
@EnableJpaAuditing을 애플리케이션에 적용하고, createdAt/updatedAt을 자동 기록하는 BaseEntity를 추가한다.
CORS, Security, Swagger 설정
build.gradle, global/config/CorsConfig.java, global/config/SecurityConfig.java, global/config/SwaggerConfig.java
springdoc-openapi-starter-webmvc-ui 의존성 추가 후, CORS(허용 오리진/메서드/헤더), Security(CSRF 비활성화, /test/**·Swagger 경로 무인증), Swagger(JWT Bearer 스키마, 프로파일별 서버 URL 분기, persistAuthorization)를 구성한다.
로컬 테스트 컨트롤러
global/common/TestController.java
local 프로파일에서만 활성화되는 TestController를 추가해 성공/생성/무콘텐츠 응답 및 비즈니스·서버·바인딩 예외 재현 엔드포인트를 제공한다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 Swagger, 공통 응답, 예외 처리, Security, CORS 초기 설정이라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed 템플릿의 주요 섹션을 모두 포함하고 있어 구조가 적절하며, 일부 항목이 비어 있어도 전체적으로 충분히 완성됐습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/1

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/main/java/com/opensource/docgrid/global/config/SwaggerConfig.java (1)

59-63: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Swagger 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6beb79f and 8376ce6.

📒 Files selected for processing (13)
  • build.gradle
  • src/main/java/com/opensource/docgrid/DocgridApplication.java
  • src/main/java/com/opensource/docgrid/global/common/TestController.java
  • src/main/java/com/opensource/docgrid/global/common/entity/BaseEntity.java
  • src/main/java/com/opensource/docgrid/global/common/response/ApiResponse.java
  • src/main/java/com/opensource/docgrid/global/common/response/ErrorResponse.java
  • src/main/java/com/opensource/docgrid/global/common/response/ResponseUtils.java
  • src/main/java/com/opensource/docgrid/global/config/CorsConfig.java
  • src/main/java/com/opensource/docgrid/global/config/SecurityConfig.java
  • src/main/java/com/opensource/docgrid/global/config/SwaggerConfig.java
  • src/main/java/com/opensource/docgrid/global/exception/DocGridException.java
  • src/main/java/com/opensource/docgrid/global/exception/ErrorCode.java
  • src/main/java/com/opensource/docgrid/global/exception/GlobalExceptionHandler.java

Comment on lines +20 to +21
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private final LocalDateTime timestamp = LocalDateTime.now();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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은 시간대가 없는 객체이므로 @JsonFormattimezone 속성을 적용해도 시간대 변환 효과는 발생하지 않습니다 [2][3].

Citations:


🏁 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 || true

Repository: DocGrid/backend

Length of output: 5427


LocalDateTimetimezone를 붙여도 KST 보장이 안 됩니다. ApiResponse.javaErrorResponse.javatimestampLocalDateTime.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` 필드와 응답 생성 로직을 함께 수정해 서버 기본 시간대에 의존하지 않도록
정리하세요.

Comment on lines +24 to +25
public <T> ResponseEntity<ApiResponse<T>> noContent() {
return ResponseEntity.noContent().build();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Suggested change
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`를 잡지 않도록 분리하면 됩니다.

Comment on lines +40 to +46
@GetMapping("/created")
public ResponseEntity<ApiResponse<SampleData>> created() {
SampleData data = SampleData.builder()
.id("docgrid-new")
.message("리소스 생성")
.build();
return ResponseUtils.created(data);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +56 to +84
@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));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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

Comment on lines +116 to +124
@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));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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:


인증 실패 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant