Skip to content

Feat/#11 - #12

Open
jhlarry1109 wants to merge 5 commits into
mainfrom
feat/#11
Open

Feat/#11#12
jhlarry1109 wants to merge 5 commits into
mainfrom
feat/#11

Conversation

@jhlarry1109

@jhlarry1109 jhlarry1109 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

#️⃣ 연관된 이슈

관련된 이슈 번호를 작성해주세요.
예시: #12, #34
Closes #11


📝 작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요.
필요시 코드 블록이나 이미지 첨부도 가능합니다.

  • 네이버, 카카오 oauth

💻 스크린샷 (선택)

UI 변경사항이 있다면 스크린샷을 첨부해주세요.


💬 리뷰 요청 사항 (선택)

리뷰어가 집중해서 봐줬으면 하는 부분이 있다면 작성해주세요.
예시: “함수 네이밍이 적절한지 봐주세요.”, “이 로직의 효율성에 대한 의견이 궁금합니다.”


Summary by CodeRabbit

  • 새 기능

    • 네이버와 카카오 소셜 로그인 및 OAuth 콜백을 지원합니다.
    • OAuth 인증 결과에 따라 공통 로그인 응답과 JWT를 제공합니다.
    • 관련 API 명세와 오류 응답 형식을 확장했습니다.
  • 버그 수정

    • 인증 실패, 잘못된 요청, 외부 서비스 오류를 일관된 HTTP 응답으로 제공합니다.
    • 토큰 검증 및 사용자 인증 오류 처리가 개선되었습니다.
  • 문서

    • 예외 처리 방식과 오류 코드 매핑 정보를 문서화했습니다.
  • 배포

    • 기본 브랜치 변경 시 인증 애플리케이션이 자동 배포됩니다.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

네이버·카카오 OAuth 인증이 추가되어 인가 URL 생성, 콜백 처리, 사용자 정보 조회, 사용자 인증 및 JWT 발급을 지원합니다. 인증 예외와 다운스트림 오류의 전역 응답 처리가 도입되었고, API 문서와 ECS 배포 워크플로가 확장되었습니다.

Changes

OAuth 인증 확장

Layer / File(s) Summary
OAuth 계약 및 설정
API_SPEC.yaml, src/main/java/com/example/auth/global/client/dto/..., src/main/resources/application.yml, src/test/resources/application.yml, src/main/java/com/example/auth/global/config/SecurityConfig.java
Naver·Kakao OAuth 경로, 프로필·토큰 DTO, provider 설정 및 공개 접근 경로가 추가되었습니다.
Provider 클라이언트 및 응답 매핑
src/main/java/com/example/auth/global/client/NaverOauthClient.java, src/main/java/com/example/auth/global/client/KakaoOauthClient.java, src/main/java/com/example/auth/global/client/dto/response/...
인가 URL 생성, 토큰 교환, Bearer 사용자 정보 조회와 provider 응답의 OauthUserProfile 변환이 구현되었습니다.
OAuth 엔드포인트 및 로그인 orchestration
src/main/java/com/example/auth/controller/AuthController.java, src/main/java/com/example/auth/service/AuthService.java, src/main/java/com/example/auth/service/AuthValidator.java, src/main/java/com/example/auth/global/client/dto/request/UserOauthRequest.java
컨트롤러가 Naver·Kakao OAuth 요청과 콜백을 제공하며, 서비스가 공통 프로필 인증과 토큰 발급 흐름을 수행합니다.
예외 전파 및 API 오류 응답
src/main/java/com/example/auth/global/exception/..., src/main/java/com/example/auth/global/client/UserServiceClient.java, src/main/java/com/example/auth/global/dto/ApiResponse.java, src/main/java/com/example/auth/service/TokenService.java, API_SPEC.yaml, docs/...
인증·OAuth·토큰·다운스트림 예외를 HTTP 상태와 ApiResponse 실패 응답으로 변환하고 관련 문서와 OpenAPI 오류 응답을 추가했습니다.
배포 자동화
.github/workflows/deploy.yml
main push 또는 수동 실행 시 Docker 이미지를 ECR에 게시하고 ECS 서비스를 강제 재배포한 뒤 stable 상태를 기다립니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthController
  participant AuthService
  participant OAuthProvider
  participant UserServiceClient
  participant TokenService
  Client->>AuthController: OAuth 시작 요청
  AuthController->>AuthService: 인가 URI 생성
  AuthService->>OAuthProvider: provider 인가 리다이렉트
  OAuthProvider->>AuthController: code, state 콜백
  AuthController->>AuthService: provider 로그인 처리
  AuthService->>OAuthProvider: 토큰 및 사용자 정보 조회
  AuthService->>UserServiceClient: OAuth 프로필 인증
  AuthService->>TokenService: JWT 발급
  TokenService-->>Client: OAuth 로그인 응답
Loading

Possibly related PRs

  • MomentLit/Auth#2: 기존 Google OAuth 흐름을 확장해 Naver·Kakao OAuth 엔드포인트와 콜백 처리를 추가한 변경과 직접적으로 연결됩니다.

Poem

당근처럼 OAuth 길이 열리고
네이버, 카카오 토큰이 춤추네
예외는 고운 응답으로 둥글게
ECS 배포도 깡총 완료! 🐰

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 배포용 GitHub Actions 워크플로는 네이버·카카오 OAuth 추가라는 이슈 범위와 직접 관련이 없습니다. 배포 워크플로는 별도 PR로 분리하고, 현재 PR에는 OAuth 구현과 필수 연동 변경만 남겨주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 제목이 변경의 핵심인 네이버/카카오 OAuth 추가를 전혀 드러내지 않아 너무 모호합니다. 제목을 '네이버·카카오 OAuth 추가'처럼 핵심 변경이 드러나는 구체적인 문구로 바꿔주세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed 이슈 번호와 작업 내용 섹션이 모두 있고, 필수 구조를 대체로 충족합니다.
Linked Issues check ✅ Passed 직접 이슈 #11의 요구인 네이버·카카오 OAuth 추가가 컨트롤러, 클라이언트, 서비스, 설정에 반영되었습니다.
✨ 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 feat/#11

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 15

🤖 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 @.github/workflows/deploy.yml:
- Line 15: 배포 워크플로의 IMAGE_TAG를 latest 대신 커밋 SHA 또는 ECR digest 기반의 불변 값으로 변경하세요.
해당 이미지 URI를 사용해 새 task definition revision을 등록하고, 배포 서비스가 그 revision을 명시적으로
사용하도록 업데이트하세요.
- Around line 17-20: Update the deploy job configuration near the jobs.deploy
definition to explicitly set GITHUB_TOKEN permissions to read-only for
repository contents, while preserving the existing build and deployment steps.

In `@API_SPEC.yaml`:
- Around line 20-76: Update the OAuth start and callback response definitions
for the Naver and Kakao endpoints in API_SPEC.yaml: define the 302 responses
with a required Location header, and define each 200 response as
application/json using the ApiResponse<OauthGoogleCallbackResponse>-equivalent
schema with all returned fields. Preserve the existing status codes and error
response references.
- Around line 79-90: Update the ErrorResponse schema to add a required string
code field alongside message, while retaining data as needed; then align all
error examples and global exception responses with the same code-and-message
format.

In `@docs/service-overview.md`:
- Around line 46-47: 서비스 개요 문서의 오래된 설명을 현재 구현에 맞게 갱신하세요.
`com.example.auth.global.exception`, `AuthException`, `GlobalExceptionHandler`를
포함한 패키지 구조를 반영하고, controller·service·DTO 구성, 주요 HTTP API, OAuth 인증 동작, 테스트 현황을
실제 코드와 일치하도록 수정하세요.

In `@docs/service-policy.md`:
- Around line 40-41: Update the authentication error mapping documentation to
list NaverOauthException and KakaoOauthException separately with 502 Bad
Gateway, matching their global handler behavior and the API_SPEC.yaml contract.
Keep unrelated AuthException cases under the 500 Internal Server Error entry.

In `@src/main/java/com/example/auth/controller/AuthController.java`:
- Around line 60-76: Update AuthController.naverOauth to generate an
unpredictable server-side state, bind it to the user session or a short-lived
signed SameSite cookie, and pass that generated value to
authService.createNaverAuthorizationUri instead of trusting the request
parameter. In naverOauthCallback, validate and consume the stored state exactly
once before calling authService.loginWithNaver, rejecting missing, mismatched,
expired, or replayed values.

In `@src/main/java/com/example/auth/global/client/KakaoOauthClient.java`:
- Around line 76-89: Update KakaoOauthClient methods requestToken() and
requestUserInfo() to catch RestClientException, including
RestClientResponseException, from the Kakao API calls and rethrow
KakaoOauthException while preserving the original cause. Ensure loginWithKakao()
receives the translated exception so the existing Kakao-specific handler returns
502 instead of allowing the error to reach the generic 500 handler.

In `@src/main/java/com/example/auth/global/client/NaverOauthClient.java`:
- Line 34: Update the RestClient initialization in NaverOauthClient so it no
longer uses the unconfigured RestClient.create() instance. Inject or reuse a
shared RestClient bean configured with explicit connection and response
timeouts, and use that client for all Naver OAuth and profile calls.
- Around line 69-82: Update NaverOauthClient methods requestToken() and
requestUserInfo() to catch exceptions raised by restClient.retrieve() or
response conversion and wrap them in NaverOauthException. Preserve the existing
request and response handling for successful calls so provider failures reach
the global NaverOauthException mapping.

In `@src/main/java/com/example/auth/global/client/UserServiceClient.java`:
- Around line 75-101: Remove raw downstream response-body logging and
propagation from UserServiceClient.convertToDownstreamException, retaining only
the status code and safe error classification. Update
src/main/java/com/example/auth/global/client/UserServiceClient.java lines 75-101
to stop including responseBody in logs and the DownstreamServiceException
constructor; update
src/main/java/com/example/auth/global/exception/DownstreamServiceException.java
lines 7-23 to remove the raw-body field and related constructor/accessor
handling, ensuring callers use only safe diagnostic information.

In `@src/main/java/com/example/auth/global/dto/ApiResponse.java`:
- Around line 3-10: Update ApiResponse and the global exception-handler paths
using ApiResponse.fail so error responses include a consistent code and message
alongside data. Add the code field to the response model, propagate the
appropriate error code through fail callers, and update the corresponding error
schema in API_SPEC.yaml to match the serialized contract.

In `@src/main/java/com/example/auth/global/exception/GlobalExceptionHandler.java`:
- Around line 66-95: GlobalExceptionHandler의 세 downstream 예외 분기에서
e.getResponseBody()를 로그 인자로 전달하지 않도록 제거하세요. 각 log.warn/log.error는 서비스명·상태 코드 등
안전한 메타데이터와 기존 요청 추적 ID가 있다면 마스킹 가능한 값만 남기고, ApiResponse 반환 동작은 변경하지 마세요.

In `@src/main/java/com/example/auth/service/AuthService.java`:
- Around line 108-148: Update createNaverAuthorizationUri and
createKakaoAuthorizationUri to generate and store a server-side OAuth state with
a short TTL, and pass that generated value to the provider authorization URI. In
loginWithNaver and loginWithKakao, validate and consume the callback state once
against the stored value before requestToken, rejecting missing, mismatched, or
expired states.

In `@src/main/java/com/example/auth/service/TokenService.java`:
- Around line 52-58: Extend the existing try block in the refresh-token flow to
include jwtProvider.getSubject(refreshToken), so JwtException and
IllegalArgumentException from subject extraction are converted to
UnauthorizedException just like validation failures. Keep the current
unauthorized message and subject assignment behavior for successful parsing.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f326e6c-6466-4b88-b534-91196a95f6ff

📥 Commits

Reviewing files that changed from the base of the PR and between 9d6e19a and 502a10c.

📒 Files selected for processing (32)
  • .github/workflows/deploy.yml
  • API_SPEC.yaml
  • docs/service-overview.md
  • docs/service-policy.md
  • src/main/java/com/example/auth/controller/AuthController.java
  • src/main/java/com/example/auth/global/client/KakaoOauthClient.java
  • src/main/java/com/example/auth/global/client/NaverOauthClient.java
  • src/main/java/com/example/auth/global/client/UserServiceClient.java
  • src/main/java/com/example/auth/global/client/dto/request/UserGoogleOauthRequest.java
  • src/main/java/com/example/auth/global/client/dto/request/UserOauthRequest.java
  • src/main/java/com/example/auth/global/client/dto/response/GoogleUserInfoResponse.java
  • src/main/java/com/example/auth/global/client/dto/response/KakaoTokenResponse.java
  • src/main/java/com/example/auth/global/client/dto/response/KakaoUserInfoResponse.java
  • src/main/java/com/example/auth/global/client/dto/response/NaverTokenResponse.java
  • src/main/java/com/example/auth/global/client/dto/response/NaverUserInfoResponse.java
  • src/main/java/com/example/auth/global/client/dto/response/OauthUserProfile.java
  • src/main/java/com/example/auth/global/config/SecurityConfig.java
  • src/main/java/com/example/auth/global/dto/ApiResponse.java
  • src/main/java/com/example/auth/global/exception/AuthException.java
  • src/main/java/com/example/auth/global/exception/BadRequestException.java
  • src/main/java/com/example/auth/global/exception/DownstreamServiceException.java
  • src/main/java/com/example/auth/global/exception/GlobalExceptionHandler.java
  • src/main/java/com/example/auth/global/exception/GoogleOauthException.java
  • src/main/java/com/example/auth/global/exception/KakaoOauthException.java
  • src/main/java/com/example/auth/global/exception/NaverOauthException.java
  • src/main/java/com/example/auth/global/exception/TokenNotFoundException.java
  • src/main/java/com/example/auth/global/exception/UnauthorizedException.java
  • src/main/java/com/example/auth/service/AuthService.java
  • src/main/java/com/example/auth/service/AuthValidator.java
  • src/main/java/com/example/auth/service/TokenService.java
  • src/main/resources/application.yml
  • src/test/resources/application.yml
💤 Files with no reviewable changes (1)
  • src/main/java/com/example/auth/global/client/dto/request/UserGoogleOauthRequest.java

ECR_REPOSITORY: momentlit/auth
ECS_CLUSTER: default
ECS_SERVICE: momentlit-auth-service
IMAGE_TAG: latest

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 | 🏗️ Heavy lift

불변 이미지로 배포하고 새 task definition revision을 지정하세요.

latest를 덮어쓴 뒤 같은 task definition을 강제 재배포하면, 동시에 실행된 배포에서 다른 커밋의 이미지를 가져오거나 롤백 대상이 사라질 수 있습니다. 커밋 SHA 또는 ECR digest로 태그하고, 해당 URI를 반영한 task definition revision을 등록한 뒤 서비스에 지정하세요.

Also applies to: 39-56

🤖 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 @.github/workflows/deploy.yml at line 15, 배포 워크플로의 IMAGE_TAG를 latest 대신 커밋
SHA 또는 ECR digest 기반의 불변 값으로 변경하세요. 해당 이미지 URI를 사용해 새 task definition revision을
등록하고, 배포 서비스가 그 revision을 명시적으로 사용하도록 업데이트하세요.

Comment on lines +17 to +20
jobs:
deploy:
name: Build and Deploy Auth
runs-on: ubuntu-latest

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

GitHub 토큰 권한을 읽기 전용으로 제한하세요.

permissions가 없어 저장소/조직의 기본 GITHUB_TOKEN 권한에 의존합니다. 이 workflow에는 소스 checkout 권한만 필요하므로 최소 권한을 명시하세요.

수정 예시
 on:
   push:
     branches:
       - main
   workflow_dispatch:

+permissions:
+  contents: read
+
 env:
🤖 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 @.github/workflows/deploy.yml around lines 17 - 20, Update the deploy job
configuration near the jobs.deploy definition to explicitly set GITHUB_TOKEN
permissions to read-only for repository contents, while preserving the existing
build and deployment steps.

Source: Linters/SAST tools

Comment thread API_SPEC.yaml
Comment on lines +20 to +76
responses:
"302":
description: "Redirects to Naver authorization endpoint."
/auth/oauth/naver/callback:
get:
summary: "Handle Naver OAuth callback"
parameters:
- name: code
in: query
required: true
schema:
type: string
- name: state
in: query
required: false
schema:
type: string
responses:
"200":
description: "JWT tokens returned after Naver login."
"400":
$ref: "#/components/responses/BadRequestError"
"502":
$ref: "#/components/responses/NaverOauthError"
/auth/oauth/kakao:
get:
summary: "Redirect to Kakao OAuth"
parameters:
- name: state
in: query
required: false
schema:
type: string
responses:
"302":
description: "Redirects to Kakao authorization endpoint."
/auth/oauth/kakao/callback:
get:
summary: "Handle Kakao OAuth callback"
parameters:
- name: code
in: query
required: true
schema:
type: string
- name: state
in: query
required: false
schema:
type: string
responses:
"200":
description: "JWT tokens returned after Kakao login."
"400":
$ref: "#/components/responses/BadRequestError"
"502":
$ref: "#/components/responses/KakaoOauthError"

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

OAuth 성공·리다이렉트 응답 계약을 명시하세요.

구현은 시작 엔드포인트에서 Location 헤더를 포함한 302를, 콜백에서 ApiResponse<OauthGoogleCallbackResponse> JSON을 반환하지만 명세에는 설명만 있습니다. 각 302의 Location 헤더와 각 200의 application/json 스키마·필드를 정의해야 클라이언트가 계약대로 연동할 수 있습니다.

As per coding guidelines, API_SPEC.yaml is the source of truth for paths, response fields, status codes, and error responses.

🤖 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 `@API_SPEC.yaml` around lines 20 - 76, Update the OAuth start and callback
response definitions for the Naver and Kakao endpoints in API_SPEC.yaml: define
the 302 responses with a required Location header, and define each 200 response
as application/json using the
ApiResponse<OauthGoogleCallbackResponse>-equivalent schema with all returned
fields. Preserve the existing status codes and error response references.

Source: Coding guidelines

Comment thread API_SPEC.yaml
Comment on lines +79 to +90
ErrorResponse:
type: object
properties:
message:
type: string
example: "[ERROR: Request/BadRequest] Refresh Token을 입력해주세요."
data:
nullable: true
example: null
required:
- message
- 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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

오류 응답에 code 필드를 추가하세요.

ErrorResponsemessagedata만 정의합니다. 오류를 안정적으로 분기할 수 있도록 codemessage를 필수 필드로 정의하고, 모든 오류 예시 및 전역 예외 응답도 같은 형식으로 맞추세요.

As per coding guidelines, API error responses should have a consistent format with code and message fields.

🤖 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 `@API_SPEC.yaml` around lines 79 - 90, Update the ErrorResponse schema to add a
required string code field alongside message, while retaining data as needed;
then align all error examples and global exception responses with the same
code-and-message format.

Source: Coding guidelines

Comment thread docs/service-overview.md
Comment on lines +46 to +47
Project-specific exception handling is implemented under `com.example.auth.global.exception`.
Auth-specific exceptions extend `AuthException` and are handled by `GlobalExceptionHandler`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

서비스 개요의 나머지 오래된 설명도 함께 갱신하세요.

같은 문서가 controller, service, DTO, HTTP API, 인증 동작이 없다고 설명해 현재 OAuth 구현과 모순됩니다. 패키지 구조·주요 API·OAuth 인증·테스트 현황 섹션도 현재 상태에 맞추세요.

🤖 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 `@docs/service-overview.md` around lines 46 - 47, 서비스 개요 문서의 오래된 설명을 현재 구현에 맞게
갱신하세요. `com.example.auth.global.exception`, `AuthException`,
`GlobalExceptionHandler`를 포함한 패키지 구조를 반영하고, controller·service·DTO 구성, 주요 HTTP
API, OAuth 인증 동작, 테스트 현황을 실제 코드와 일치하도록 수정하세요.

Comment on lines +75 to 101
private DownstreamServiceException convertToDownstreamException(RestClientResponseException e) {
String responseBody = e.getResponseBodyAsString();
String message = extractMessage(responseBody);
HttpStatusCode statusCode = e.getStatusCode();

if (statusCode.is4xxClientError()) {
log.warn(
"{} service client error. status={}, body={}",
SERVICE_NAME,
statusCode,
responseBody
);
} else {
log.error(
"{} service server error. status={}, body={}",
SERVICE_NAME,
statusCode,
responseBody
);
}

return new DownstreamServiceException(
SERVICE_NAME,
statusCode,
message,
responseBody
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

다운스트림 응답 본문을 저장하거나 로그에 남기지 마세요.

OAuth·사용자 인증 실패 응답에는 이메일 등 개인정보가 포함될 수 있습니다. 현재 원문 본문을 직접 로그에 기록하고 예외에 보관하여 전역 처리기에서도 다시 노출됩니다. 상태 코드와 안전한 오류 분류만 남기고, 원문 responseBody 전달·저장을 제거하거나 반드시 마스킹하세요.

  • src/main/java/com/example/auth/global/client/UserServiceClient.java#L75-L101: responseBody를 로그 포맷과 DownstreamServiceException 생성자에서 제거하세요.
  • src/main/java/com/example/auth/global/exception/DownstreamServiceException.java#L7-L23: 원문 응답 본문 필드를 제거하고, 호출부가 안전한 진단 정보만 전달하도록 변경하세요.

As per coding guidelines, “Do not expose secrets, API keys, credentials, or personal information in any code files.”

📍 Affects 2 files
  • src/main/java/com/example/auth/global/client/UserServiceClient.java#L75-L101 (this comment)
  • src/main/java/com/example/auth/global/exception/DownstreamServiceException.java#L7-L23
🤖 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/example/auth/global/client/UserServiceClient.java` around
lines 75 - 101, Remove raw downstream response-body logging and propagation from
UserServiceClient.convertToDownstreamException, retaining only the status code
and safe error classification. Update
src/main/java/com/example/auth/global/client/UserServiceClient.java lines 75-101
to stop including responseBody in logs and the DownstreamServiceException
constructor; update
src/main/java/com/example/auth/global/exception/DownstreamServiceException.java
lines 7-23 to remove the raw-body field and related constructor/accessor
handling, ensuring callers use only safe diagnostic information.

Source: Coding guidelines

Comment on lines 3 to +10
public record ApiResponse<T> (
String message,
T data
){}
){
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(message, null);
}
}

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 | 🏗️ Heavy lift

오류 응답에 code 필드를 포함하세요.

현재 fail()messagedata만 직렬화합니다. 전역 예외 처리기가 이 메서드를 사용하므로 OAuth·다운스트림 오류에 클라이언트가 처리할 수 있는 오류 코드가 없습니다. 오류 DTO 또는 응답 모델에 code를 추가하고, 핸들러와 API_SPEC.yaml의 오류 스키마를 함께 맞춰주세요.

As per coding guidelines, API error responses should have a consistent format with 'code' and 'message' fields, and API_SPEC.yaml is the source of truth for the service's API contract.

🤖 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/example/auth/global/dto/ApiResponse.java` around lines 3 -
10, Update ApiResponse and the global exception-handler paths using
ApiResponse.fail so error responses include a consistent code and message
alongside data. Add the code field to the response model, propagate the
appropriate error code through fail callers, and update the corresponding error
schema in API_SPEC.yaml to match the serialized contract.

Source: Coding guidelines

Comment on lines +66 to +95
if (e.getStatusCode().is4xxClientError()) {
log.warn(
"Downstream service client error. service={}, status={}, body={}",
e.getServiceName(),
e.getStatusCode(),
e.getResponseBody()
);

return ResponseEntity.status(e.getStatusCode())
.body(ApiResponse.fail(e.getMessage()));
}

if (e.getStatusCode().isSameCodeAs(HttpStatus.SERVICE_UNAVAILABLE)) {
log.error(
"Downstream service unavailable. service={}, body={}",
e.getServiceName(),
e.getResponseBody()
);

return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(ApiResponse.fail("[ERROR: Auth/Downstream/" + e.getServiceName()
+ "] " + e.getServiceName() + " 서비스에 연결할 수 없습니다."));
}

log.error(
"Downstream service server error. service={}, status={}, body={}",
e.getServiceName(),
e.getStatusCode(),
e.getResponseBody()
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Downstream 응답 본문을 로그에 남기지 마세요.

외부 서비스의 원문 오류 본문에는 이메일·provider ID·토큰 등이 포함될 수 있는데, 세 분기 모두 이를 그대로 기록합니다. responseBody 로깅을 제거하고 서비스명·상태 코드·요청 추적 ID처럼 마스킹 가능한 메타데이터만 남기세요.

As per coding guidelines, “Do not expose secrets, API keys, credentials, or personal information in any code files”.

🤖 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/example/auth/global/exception/GlobalExceptionHandler.java`
around lines 66 - 95, GlobalExceptionHandler의 세 downstream 예외 분기에서
e.getResponseBody()를 로그 인자로 전달하지 않도록 제거하세요. 각 log.warn/log.error는 서비스명·상태 코드 등
안전한 메타데이터와 기존 요청 추적 ID가 있다면 마스킹 가능한 값만 남기고, ApiResponse 반환 동작은 변경하지 마세요.

Source: Coding guidelines

Comment on lines +108 to +148
public URI createNaverAuthorizationUri(String state) {
return naverOauthClient.createAuthorizationUri(state);
}

public OauthGoogleCallbackResponse loginWithNaver(String code, String state) {

authValidator.validateOauthAuthorizationCode(code, "Naver");

NaverTokenResponse naverToken =
naverOauthClient.requestToken(code, state);

if (naverToken == null || !StringUtils.hasText(naverToken.accessToken())) {
throw new NaverOauthException("Naver Access Token을 발급받을 수 없습니다.");
}

NaverUserInfoResponse naverUser =
naverOauthClient.requestUserInfo(naverToken.accessToken());

return issueOauthLoginResponse(naverUser.toProfile(), "Naver");
}

public URI createKakaoAuthorizationUri(String state) {
return kakaoOauthClient.createAuthorizationUri(state);
}

public OauthGoogleCallbackResponse loginWithKakao(String code, String state) {

authValidator.validateOauthAuthorizationCode(code, "Kakao");

KakaoTokenResponse kakaoToken =
kakaoOauthClient.requestToken(code);

if (kakaoToken == null || !StringUtils.hasText(kakaoToken.accessToken())) {
throw new KakaoOauthException("Kakao Access Token을 발급받을 수 없습니다.");
}

KakaoUserInfoResponse kakaoUser =
kakaoOauthClient.requestUserInfo(kakaoToken.accessToken());

return issueOauthLoginResponse(kakaoUser.toProfile(), "Kakao");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

서버 측 OAuth state 검증을 추가하세요.

인가 시작과 콜백 모두 선택적 state를 받지만, 서버가 생성·보관한 값과 콜백 값을 비교하지 않습니다. 공격자가 만든 콜백이 로그인 요청과 연결되지 않아 OAuth 로그인 CSRF가 가능해집니다. 짧은 TTL의 서버 측 state를 생성·저장하고 토큰 교환 전에 일회성으로 검증하세요.

🤖 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/example/auth/service/AuthService.java` around lines 108 -
148, Update createNaverAuthorizationUri and createKakaoAuthorizationUri to
generate and store a server-side OAuth state with a short TTL, and pass that
generated value to the provider authorization URI. In loginWithNaver and
loginWithKakao, validate and consume the callback state once against the stored
value before requestToken, rejecting missing, mismatched, or expired states.

Comment on lines +52 to 58
try {
jwtProvider.validateToken(refreshToken);
} catch (JwtException | IllegalArgumentException exception) {
throw new UnauthorizedException("유효하지 않은 Refresh Token입니다.");
}

String subject = jwtProvider.getSubject(refreshToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -A5 -B2 'validateToken|getSubject|parseClaims' \
  src/main/java/com/example/auth/global/security/JwtProvider.java \
  src/main/java/com/example/auth/service/TokenService.java

Repository: MomentLit/Auth

Length of output: 5074


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '40,70p' src/main/java/com/example/auth/service/TokenService.java
sed -n '80,115p' src/main/java/com/example/auth/global/security/JwtProvider.java
rg -n "UnauthorizedException|ErrorResponse|ExceptionHandler|JwtException|ExpiredJwtException" src/main/java/com/example -g '*.java'

Repository: MomentLit/Auth

Length of output: 5413


🌐 Web query:

JJWT 0.12.x parseSignedClaims ExpiredJwtException verifyWith

💡 Result:

In JJWT 0.12.x, the verifyWith method is used to configure the signature verification key on a JwtParserBuilder [1][2]. The parseSignedClaims method is used to parse and verify JWS tokens [1][3]. When using verifyWith and parseSignedClaims, if the token is expired, the parser will throw an ExpiredJwtException [3][4]. To handle this, you should wrap the parsing call in a try-catch block, catching JwtException or specifically catching ExpiredJwtException to handle expiration differently from other validation failures [1][5]. Example usage: try { Jws jws = Jwts.parser.verifyWith(key) // Configures the key for signature verification.build.parseSignedClaims(tokenString); // If successful, you can access the claims Claims claims = jws.getPayload; } catch (ExpiredJwtException e) { // Handle expired token specifically } catch (JwtException e) { // Handle other JWT-related failures (signature mismatch, malformed, etc.) } The verifyWith method replaces older configuration methods like setSigningKey [6]. Because JJWT 0.12.x parsers are immutable, you must use the builder pattern (Jwts.parser...build) to configure and instantiate the parser [6]. If you encounter an ExpiredJwtException, it indicates that the expiration time (exp) claim in the token is before the time the parsing method was invoked [3].

Citations:


subject 추출까지 동일한 JWT 예외 처리 범위에 넣으세요.

validateToken() 뒤에 getSubject()가 다시 parseClaims()를 호출합니다. 검증 직후 만료 시 expiredJwtException이 Line 58에서 JwtException | IllegalArgumentException 바깥으로 빠져 500으로 처리됩니다. subject 추출까지 같은 try 블록에 넣거나 한 번의 claims 파싱 결과를 재사용하세요.

🤖 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/example/auth/service/TokenService.java` around lines 52 -
58, Extend the existing try block in the refresh-token flow to include
jwtProvider.getSubject(refreshToken), so JwtException and
IllegalArgumentException from subject extraction are converted to
UnauthorizedException just like validation failures. Keep the current
unauthorized message and subject assignment behavior for successful parsing.

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.

[✨ Feature] 네이버, 카카오 oauth

1 participant