Feat/#11 - #12
Conversation
📝 WalkthroughWalkthrough네이버·카카오 OAuth 인증이 추가되어 인가 URL 생성, 콜백 처리, 사용자 정보 조회, 사용자 인증 및 JWT 발급을 지원합니다. 인증 예외와 다운스트림 오류의 전역 응답 처리가 도입되었고, API 문서와 ECS 배포 워크플로가 확장되었습니다. ChangesOAuth 인증 확장
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 로그인 응답
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (32)
.github/workflows/deploy.ymlAPI_SPEC.yamldocs/service-overview.mddocs/service-policy.mdsrc/main/java/com/example/auth/controller/AuthController.javasrc/main/java/com/example/auth/global/client/KakaoOauthClient.javasrc/main/java/com/example/auth/global/client/NaverOauthClient.javasrc/main/java/com/example/auth/global/client/UserServiceClient.javasrc/main/java/com/example/auth/global/client/dto/request/UserGoogleOauthRequest.javasrc/main/java/com/example/auth/global/client/dto/request/UserOauthRequest.javasrc/main/java/com/example/auth/global/client/dto/response/GoogleUserInfoResponse.javasrc/main/java/com/example/auth/global/client/dto/response/KakaoTokenResponse.javasrc/main/java/com/example/auth/global/client/dto/response/KakaoUserInfoResponse.javasrc/main/java/com/example/auth/global/client/dto/response/NaverTokenResponse.javasrc/main/java/com/example/auth/global/client/dto/response/NaverUserInfoResponse.javasrc/main/java/com/example/auth/global/client/dto/response/OauthUserProfile.javasrc/main/java/com/example/auth/global/config/SecurityConfig.javasrc/main/java/com/example/auth/global/dto/ApiResponse.javasrc/main/java/com/example/auth/global/exception/AuthException.javasrc/main/java/com/example/auth/global/exception/BadRequestException.javasrc/main/java/com/example/auth/global/exception/DownstreamServiceException.javasrc/main/java/com/example/auth/global/exception/GlobalExceptionHandler.javasrc/main/java/com/example/auth/global/exception/GoogleOauthException.javasrc/main/java/com/example/auth/global/exception/KakaoOauthException.javasrc/main/java/com/example/auth/global/exception/NaverOauthException.javasrc/main/java/com/example/auth/global/exception/TokenNotFoundException.javasrc/main/java/com/example/auth/global/exception/UnauthorizedException.javasrc/main/java/com/example/auth/service/AuthService.javasrc/main/java/com/example/auth/service/AuthValidator.javasrc/main/java/com/example/auth/service/TokenService.javasrc/main/resources/application.ymlsrc/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 |
There was a problem hiding this comment.
🎯 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을 명시적으로 사용하도록 업데이트하세요.
| jobs: | ||
| deploy: | ||
| name: Build and Deploy Auth | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
🔒 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
| 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" |
There was a problem hiding this comment.
🗄️ 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
| ErrorResponse: | ||
| type: object | ||
| properties: | ||
| message: | ||
| type: string | ||
| example: "[ERROR: Request/BadRequest] Refresh Token을 입력해주세요." | ||
| data: | ||
| nullable: true | ||
| example: null | ||
| required: | ||
| - message | ||
| - data |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
오류 응답에 code 필드를 추가하세요.
ErrorResponse는 message와 data만 정의합니다. 오류를 안정적으로 분기할 수 있도록 code와 message를 필수 필드로 정의하고, 모든 오류 예시 및 전역 예외 응답도 같은 형식으로 맞추세요.
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
| Project-specific exception handling is implemented under `com.example.auth.global.exception`. | ||
| Auth-specific exceptions extend `AuthException` and are handled by `GlobalExceptionHandler`. |
There was a problem hiding this comment.
📐 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 인증 동작, 테스트 현황을 실제 코드와 일치하도록 수정하세요.
| 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 | ||
| ); |
There was a problem hiding this comment.
🔒 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
| public record ApiResponse<T> ( | ||
| String message, | ||
| T data | ||
| ){} | ||
| ){ | ||
| public static <T> ApiResponse<T> fail(String message) { | ||
| return new ApiResponse<>(message, null); | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
오류 응답에 code 필드를 포함하세요.
현재 fail()은 message와 data만 직렬화합니다. 전역 예외 처리기가 이 메서드를 사용하므로 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
| 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() | ||
| ); |
There was a problem hiding this comment.
🔒 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
| 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"); | ||
| } |
There was a problem hiding this comment.
🔒 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.
| try { | ||
| jwtProvider.validateToken(refreshToken); | ||
| } catch (JwtException | IllegalArgumentException exception) { | ||
| throw new UnauthorizedException("유효하지 않은 Refresh Token입니다."); | ||
| } | ||
|
|
||
| String subject = jwtProvider.getSubject(refreshToken); |
There was a problem hiding this comment.
🩺 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.javaRepository: 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:
- 1: https://github.com/jwtk/jjwt/blob/0.12.5/README.md
- 2: https://javadoc.io/static/io.jsonwebtoken/jjwt-api/0.12.7/io/jsonwebtoken/JwtParserBuilder.html
- 3: https://javadoc.io/static/io.jsonwebtoken/jjwt-api/0.12.7/io/jsonwebtoken/JwtParser.html
- 4: https://javadoc.io/static/io.jsonwebtoken/jjwt-api/0.12.3/io/jsonwebtoken/JwtParser.html
- 5: https://github.com/jwtk/jjwt/blob/0.12.0/README.md
- 6: Having trouble upgrading from an old version of jjwt jwtk/jjwt#955
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.
#️⃣ 연관된 이슈
📝 작업 내용
💻 스크린샷 (선택)
💬 리뷰 요청 사항 (선택)
Summary by CodeRabbit
새 기능
버그 수정
문서
배포