Skip to content

[FEAT] 소셜 로그인 및 인증 api 구현 - #61

Merged
on1yoneprivate merged 53 commits into
developfrom
feat/#42-social-login-completion
Jul 29, 2026
Merged

[FEAT] 소셜 로그인 및 인증 api 구현#61
on1yoneprivate merged 53 commits into
developfrom
feat/#42-social-login-completion

Conversation

@kimyw1018

@kimyw1018 kimyw1018 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

📍 개요

카카오, 구글 OAuth 2.0 Access Token 소셜 로그인 통합 API,
JWT(Access/Refresh Token) 인증 인프라 구현

⛓️‍💥 관련 이슈


🛠️ 작업 내용

  • 소셜 로그인 / 회원가입 통합 API /api/auth/login/{socialType} 구현
    • 카카오 및 구글 API 서버로부터 유저 정보를 가져오는 OAuthClientService 연동
    • 신규 회원 가입 시 User 및 SocialAuth 엔티티 생성 및 매핑 처리
  • 인증 도메인 핵심 보안 및 토큰 관리 기능 구현
    • Refresh Token Rotation 토큰 재발급 API 구현
    • DB 내 원본 토큰 노출 방지를 위한 SHA-256 해시값 저장 및 비교 검증
    • 세션 무효화 및 만료 시각을 갱신 로그아웃 및 회원 탈퇴 API 구현

🔥 리뷰 요청 사항

리뷰어가 중점적으로 확인해주었으면 하는 내용을 작성해주세요.

  • 토큰 회전 정책: 토큰 재발급 시 기존 Refresh Token 폐기, 새 Refresh Token 발급으로 DB 해시값 갱신 로직 흐름이 맞는지 봐주세욥

✅ 체크리스트

  • 코드 컨벤션을 준수했습니다.
  • 불필요한 코드 및 import를 제거했습니다.
  • 예외 처리를 적용했습니다.
  • 테스트를 완료했습니다.
  • 관련 Issue를 연결했습니다.

📎 참고 사항

Summary by CodeRabbit

  • 새로운 기능

    • 카카오·구글 소셜 로그인 연동을 강화했습니다.
    • 액세스 토큰 재발급, 로그아웃, 회원 탈퇴 기능을 추가했습니다.
    • 로그인 시 기기 정보를 반영하고, 로그인 응답에 온보딩 완료 여부를 제공합니다.
    • 회원별 다중 로그인 세션과 특정 기기 세션 만료를 지원합니다.
  • 개선 사항

    • 소셜 인증 오류를 상황에 따라 구분해 안내합니다.
    • 회원 탈퇴 시 연결된 소셜 인증 정보와 세션이 함께 정리됩니다.
    • 인증 관련 주요 흐름에 대한 테스트를 보강했습니다.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kimyw1018, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bfdb721-799b-42c1-ad65-65e20ab03654

📥 Commits

Reviewing files that changed from the base of the PR and between 2a3f24e and fc257ef.

📒 Files selected for processing (6)
  • src/main/java/com/mr/domain/auth/controller/AuthController.java
  • src/main/java/com/mr/domain/auth/exception/OAuthExceptionMapper.java
  • src/main/java/com/mr/domain/auth/service/AuthService.java
  • src/main/java/com/mr/domain/auth/service/OAuthClientService.java
  • src/test/java/com/mr/domain/auth/service/AuthServiceTest.java
  • src/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java
📝 Walkthrough

Walkthrough

OAuth 제공자 사용자 조회, 소셜 인증 정보와 토큰 상태 저장, JWT 재발급·로그아웃·탈퇴 처리가 구현되었습니다. 인증 DTO와 엔드포인트가 확장되었고, OAuth 오류 매핑 및 관련 통합 테스트가 추가되었습니다.

Changes

소셜 로그인 및 JWT 인증 흐름

Layer / File(s) Summary
인증 계약과 도메인 모델
src/main/java/com/mr/domain/auth/dto/..., src/main/java/com/mr/domain/auth/entity/..., src/main/java/com/mr/domain/auth/repository/..., src/main/java/com/mr/domain/statistics/..., src/main/java/com/mr/domain/user/...
요청·응답 DTO, 소셜 타입, OAuth 사용자 정보가 정리되고, SocialAuthUser 연관관계와 해시 기반 refresh token 상태를 사용하도록 변경되었습니다.
OAuth 제공자 조회
src/main/java/com/mr/domain/auth/service/OAuthClientService.java, src/main/java/com/mr/domain/auth/dto/res/*, src/main/java/com/mr/global/config/OAuthRestClientConfig.java, src/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java
Kakao·Google 사용자 정보 API 호출, 응답 매핑, timeout 설정과 OAuth 오류 변환이 추가되었으며 관련 테스트가 작성되었습니다.
로그인과 토큰 수명주기
src/main/java/com/mr/domain/auth/service/AuthService.java, src/main/java/com/mr/global/security/jwt/JwtTokenProvider.java, src/test/java/com/mr/domain/auth/service/AuthServiceTest.java
실제 OAuth 기반 로그인, 신규 사용자 등록, 토큰 해시 저장, 재발급·로그아웃·탈퇴 처리가 구현되었고 DB 기반 흐름이 테스트되었습니다.
인증 API와 실행 설정
src/main/java/com/mr/domain/auth/controller/AuthController.java, src/main/java/com/mr/domain/auth/dto/req/AuthRequestDTO.java, src/main/java/com/mr/global/security/SecurityConfig.java
socialLogin에 기기 정보가 추가되고 reissue, logout, withdraw 엔드포인트가 노출되었으며 /api/auth/reissue가 공개 경로로 등록되었습니다.

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

Possibly related PRs

  • Musereview/BE#14: SocialAuth, SocialType, 토큰 만료·갱신 계약을 함께 변경합니다.
  • Musereview/BE#41: 기존 socialLogin 엔드포인트를 확장하는 코드 흐름과 직접 연결됩니다.
  • Musereview/BE#49: 동일한 AuthController.socialLogin 메서드의 API 메타데이터를 변경합니다.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthController
  participant AuthService
  participant OAuthClientService
  participant SocialAuthRepository

  Client->>AuthController: 소셜 로그인 요청
  AuthController->>AuthService: socialLogin(socialType, accessToken, deviceInfo)
  AuthService->>OAuthClientService: getUserInfo(...)
  OAuthClientService-->>AuthService: OAuthUserInfo
  AuthService->>SocialAuthRepository: 인증 정보 조회 또는 토큰 해시 저장
  AuthService-->>AuthController: LoginResponse
  AuthController-->>Client: ApiResponse
Loading

Poem

토큰은 새 옷을 입고,
소셜 계정 길을 열고,
만료된 세션은 잠들고,
OAuth 응답은 노래하네.
로그인 흐름, 착착 완료! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 인증 기능과 직접 관련 없는 UserStatisticsRepository와 UserProfileService 수정이 포함돼 범위를 일부 벗어납니다. 통계 조회 변경이 auth 구현에 필수인지 근거를 보강하거나, 별도 개선 PR로 분리해 주세요.
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 (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 소셜 로그인과 인증 API 구현이라는 변경의 핵심을 간결하게 잘 요약합니다.
Linked Issues check ✅ Passed 카카오/구글 OAuth 연동, 소셜 로그인 완성, JWT·Security 보완이 이슈 #42의 요구와 맞습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#42-social-login-completion

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/mr/domain/auth/entity/SocialAuth.java (1)

55-88: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

리프레시 토큰 "원본"이 여전히 평문으로 저장되고 있어요 — PR 목표와 정면으로 배치됩니다.

PR 설명에 명시된 목표는 "Refresh Token 원본 대신 SHA-256 해시 저장 및 검증"인데, 실제로는 생성자(63~69번째 줄 부근)와 create()refreshToken(원본)과 refreshTokenHash(해시)를 동시에 저장합니다. reissueToken()에서 실제 인증 검증은 오직 getRefreshTokenHash()만 사용하므로, 저장된 원본 토큰(refreshToken 컬럼)은 인증 로직 어디에도 쓰이지 않는 죽은 데이터이면서 동시에 DB 유출 시 즉시 악용 가능한 완전한 인증 자산입니다. 해시를 저장하는 이유(탈취 대비)가 원본을 나란히 저장하는 순간 완전히 무력화됩니다.

파라미터명이 encryptedToken인 것도 오해의 소지가 있습니다 — 실제로는 암호화가 전혀 적용되지 않은 순수 JWT 원문(tokenProvider.createRefreshToken(...)의 반환값)이 그대로 들어갑니다.

원본을 서버가 별도로 보관해야 할 이유가 없다면(클라이언트가 이미 들고 있고, 서버는 해시 비교만 하면 충분), refreshToken 컬럼과 관련 파라미터를 완전히 제거하는 것을 권장드립니다. 참고: OWASP Session Management Cheat Sheet의 "세션 식별자는 해시로만 저장" 권고와 동일한 원칙입니다.

🔒 제안: 원본 저장 제거
-    `@Column`(name = "refresh_token", length = 1000)
-    private String refreshToken;
-
     `@Column`(name = "refresh_token_hash", length = 64, unique = true)
     private String refreshTokenHash;
@@
-    private SocialAuth(User user, SocialType socialType, String socialId, String refreshToken,
-                       String refreshTokenHash, LocalDateTime expiredAt, String deviceInfo) {
+    private SocialAuth(User user, SocialType socialType, String socialId,
+                       String refreshTokenHash, LocalDateTime expiredAt, String deviceInfo) {
         validateUser(user);
         validateSocialType(socialType);
         validateSocialId(socialId);
 
         this.user = user;
         this.socialType = socialType;
         this.socialId = socialId;
-        this.refreshToken = refreshToken;
         this.refreshTokenHash = refreshTokenHash;
         this.expiredAt = expiredAt;
         this.deviceInfo = deviceInfo;
     }
 
-    public static SocialAuth create(User user, SocialType socialType, String socialId,
-                                    String encryptedToken, String tokenHash, LocalDateTime expiredAt, String deviceInfo) {
-        validateTokenValue(encryptedToken);
+    public static SocialAuth create(User user, SocialType socialType, String socialId,
+                                    String tokenHash, LocalDateTime expiredAt, String deviceInfo) {
         validateTokenValue(tokenHash);
         validateExpiryTime(expiredAt);
 
         return SocialAuth.builder()
                 .user(user)
                 .socialType(socialType)
                 .socialId(socialId)
-                .refreshToken(encryptedToken)
                 .refreshTokenHash(tokenHash)
                 .expiredAt(expiredAt)
                 .deviceInfo(deviceInfo)
                 .build();
     }

updateRefreshToken, AuthService(원본 토큰은 응답 DTO로 클라이언트에만 전달하고 저장은 하지 않도록)도 함께 정리해야 합니다.

🤖 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/mr/domain/auth/entity/SocialAuth.java` around lines 55 -
88, Remove plaintext refresh-token persistence from SocialAuth: eliminate the
refreshToken field/column and related constructor, builder, create, and
updateRefreshToken parameters, retaining only refreshTokenHash for validation.
Update AuthService and response handling so the original token is returned to
the client when needed but is never passed into or stored by SocialAuth, and
adjust all callers and accessors accordingly.
🧹 Nitpick comments (5)
src/main/java/com/mr/domain/auth/service/OAuthClientService.java (2)

56-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

@Slf4j를 import했는데 정작 예외를 로깅하지 않고 있어요.

두 메서드 모두 catch (Exception e)에서 원인 예외를 그대로 삼키고 GeneralException(INVALID_INPUT_VALUE)만 던집니다. 운영 중 로그인 실패가 발생해도 원인(카카오/구글 응답 파싱 실패인지, 네트워크 문제인지, 만료된 토큰인지)을 로그로 추적할 수 없습니다. log.warn("소셜 사용자 정보 조회 실패: {}", e.getMessage(), e); 정도만 추가해도 장애 대응 시간이 크게 줄어듭니다.

Also applies to: 77-79

🤖 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/mr/domain/auth/service/OAuthClientService.java` around
lines 56 - 59, In both catch (Exception e) blocks of OAuthClientService, log the
original exception with log.warn using a descriptive social-user-information
lookup message, including the exception message and stack trace, before throwing
GeneralException(CommonStatus.INVALID_INPUT_VALUE).

63-71: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Google 응답 처리: raw Map 타입, null 체크 부재, 구식 엔드포인트.

세 가지가 한 곳에 모여 있습니다.

  1. Map response는 raw type이라 unchecked 캐스팅 경고가 발생합니다. Kakao 쪽처럼 ParameterizedTypeReference<Map<String, Object>>를 사용해주세요.
  2. responsenull인 경우에 대한 명시적 체크가 없어 response.get("id")에서 NPE가 날 수 있습니다(현재는 바깥 catch(Exception e)가 감싸서 죽지는 않지만, 원인 파악이 더 어려워집니다).
  3. 사용 중인 https://www.googleapis.com/oauth2/v2/userinfo는 구버전 엔드포인트입니다. Google's userinfo endpoint is https://www.googleapis.com/oauth2/v3/userinfo. v2도 당장 동작은 하지만, 신규 연동은 v3나 OpenID Connect 표준 엔드포인트(https://openidconnect.googleapis.com/v1/userinfo)로 맞추는 걸 권장합니다.
♻️ 제안
-            Map response = restClient.get()
-                    .uri("https://www.googleapis.com/oauth2/v2/userinfo")
+            Map<String, Object> response = restClient.get()
+                    .uri("https://www.googleapis.com/oauth2/v3/userinfo")
                     .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                     .retrieve()
-                    .body(Map.class);
+                    .body(new ParameterizedTypeReference<Map<String, Object>>() {});
+
+            if (response == null) {
+                throw new GeneralException(CommonStatus.INVALID_INPUT_VALUE);
+            }
🤖 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/mr/domain/auth/service/OAuthClientService.java` around
lines 63 - 71, Update the Google response handling in OAuthClientService to use
ParameterizedTypeReference<Map<String, Object>> instead of the raw Map type,
explicitly validate that the response is non-null before reading its fields, and
switch the userinfo URI from the v2 endpoint to
https://www.googleapis.com/oauth2/v3/userinfo. Preserve the existing extraction
of id, email, and picture after validation.
src/main/java/com/mr/domain/auth/service/AuthService.java (2)

58-84: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

registerNewUser가 생성한 초기 리프레시 토큰이 바로 버려져요.

registerNewUser 내부에서 initialToken/initialHash를 생성해 SocialAuth.create()에 넘기지만, socialLogin으로 돌아온 직후 updateRefreshToken(newAccessToken, ...)으로 곧바로 덮어씁니다. 즉 신규 가입 시마다 JWT 서명과 SHA-256 해싱을 한 번 더 불필요하게 수행하는 셈입니다. SocialAuth.create()가 토큰 값을 필수로 요구하는 제약(validateTokenValue) 때문에 어쩔 수 없이 생긴 구조로 보이는데, 토큰을 한 번만 생성해서 create()와 이후 흐름에 공유하도록 정리하면 불필요한 연산과 "왜 토큰을 두 번 만들지?"라는 혼란을 함께 줄일 수 있습니다.

🤖 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/mr/domain/auth/service/AuthService.java` around lines 58 -
84, Update the socialLogin flow and registerNewUser so the initial refresh token
is generated and hashed only once, then shared with SocialAuth.create() and the
subsequent updateRefreshToken call. Remove the duplicate token-generation path
while preserving SocialAuth.create()’s required token validation and existing
behavior for both new and existing users.

86-113: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

리프레시 토큰 재발급에 동시성 보호 장치가 없어요.

동일한 refresh token으로 두 요청이 거의 동시에 들어오면, 둘 다 같은 저장된 해시를 읽고 검증을 통과한 뒤 각각 새 토큰 쌍을 생성해 순차적으로 덮어쓸 수 있습니다(마지막에 커밋된 쪽만 유효). 심각한 취약점은 아니지만(먼저 응답받은 클라이언트의 access token 자체는 만료 전까지 여전히 유효), SocialAuth@Version을 추가해 낙관적 락으로 갱신 충돌을 감지하면 리프레시 토큰 로테이션의 원자성을 한층 더 보장할 수 있습니다.

🤖 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/mr/domain/auth/service/AuthService.java` around lines 86 -
113, Update the SocialAuth entity used by AuthService.reissueToken to include a
JPA `@Version` field, ensuring concurrent refresh-token updates trigger
optimistic-lock conflict detection. Keep the existing updateRefreshToken flow
and map the version field with the appropriate persistent numeric type.
src/main/java/com/mr/domain/auth/dto/res/AuthResponseDTO.java (1)

14-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

TokenInfoTokenResponse가 완전히 동일한 구조예요 — 하나로 합쳐주세요.

두 레코드 모두 accessToken/refreshToken/accessTokenExpiresInSeconds 필드가 똑같습니다. LoginResponse.tokenInfo 필드는 TokenResponse 타입인데 정작 TokenInfo라는 별도 타입이 존재해서, 나중에 합류하는 개발자가 "둘이 뭐가 다르지?" 하며 혼란스러워할 확률이 높습니다. 하나의 레코드로 통일해서 LoginResponsereissueToken 응답 모두 같은 타입을 재사용하는 걸 권장드립니다.

♻️ 제안: `TokenInfo`로 통일
     `@Builder`
     public record TokenResponse(
             String accessToken,
             String refreshToken,
             Long accessTokenExpiresInSeconds
     ) {}
 
-    `@Builder`
-    public record TokenInfo(
-            String accessToken,
-            String refreshToken,
-            Long accessTokenExpiresInSeconds
-    ) {}
-
     `@Builder`
     public record LoginResponse(
             Long userId,
             String nickname,
             boolean isNewUser,
             boolean isOnboardingCompleted,
-            TokenResponse tokenInfo
+            TokenInfo tokenInfo
     ) {}

(그리고 AuthService.reissueToken()TokenInfo.builder()를, socialLogin()이 동일하게 TokenInfo.builder()를 사용하도록 통일)

🤖 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/mr/domain/auth/dto/res/AuthResponseDTO.java` around lines
14 - 26, AuthResponseDTO의 중복 토큰 응답 타입을 TokenInfo 하나로 통합하세요.
LoginResponse.tokenInfo와 AuthService의 reissueToken 및 socialLogin 흐름이 모두
TokenInfo를 사용하도록 TokenResponse 참조와 관련 builder 호출을 변경하고, 중복된 TokenResponse 레코드는
제거하세요.
🤖 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/mr/domain/auth/controller/AuthController.java`:
- Around line 46-53: Update the security configuration’s PUBLIC_URLS (or
equivalent JwtAuthenticationFilter exclusion) to include /api/auth/reissue,
while keeping AuthController.reissue and authService.reissueToken based solely
on the refresh token so expired Bearer tokens do not block the request.

In `@src/main/java/com/mr/domain/auth/service/AuthService.java`:
- Around line 29-56: Replace the time-based isNewUser calculation in socialLogin
with an explicit flag set by the socialAuthRepository lookup: mark the value
true only when registerNewUser creates the account in the orElseGet branch, and
false for an existing social login. Use that flag when building LoginResponse
and remove the createdAt/time-window heuristic.

In `@src/main/java/com/mr/domain/auth/service/OAuthClientService.java`:
- Line 42: Update the socialId extraction in OAuthClientService to preserve a
missing Kakao id as null instead of converting it to the literal string "null",
so SocialAuth.validateSocialId can reject absent or blank identifiers before
account creation.
- Line 19: Update the RestClient initialization in OAuthClientService to use a
ClientHttpRequestFactory with explicit connect and read timeouts, following the
existing application.yml timeout configuration pattern used for the AI client.
Preserve the current OAuth request behavior while ensuring stalled Kakao/Google
calls terminate within bounded time.

---

Outside diff comments:
In `@src/main/java/com/mr/domain/auth/entity/SocialAuth.java`:
- Around line 55-88: Remove plaintext refresh-token persistence from SocialAuth:
eliminate the refreshToken field/column and related constructor, builder,
create, and updateRefreshToken parameters, retaining only refreshTokenHash for
validation. Update AuthService and response handling so the original token is
returned to the client when needed but is never passed into or stored by
SocialAuth, and adjust all callers and accessors accordingly.

---

Nitpick comments:
In `@src/main/java/com/mr/domain/auth/dto/res/AuthResponseDTO.java`:
- Around line 14-26: AuthResponseDTO의 중복 토큰 응답 타입을 TokenInfo 하나로 통합하세요.
LoginResponse.tokenInfo와 AuthService의 reissueToken 및 socialLogin 흐름이 모두
TokenInfo를 사용하도록 TokenResponse 참조와 관련 builder 호출을 변경하고, 중복된 TokenResponse 레코드는
제거하세요.

In `@src/main/java/com/mr/domain/auth/service/AuthService.java`:
- Around line 58-84: Update the socialLogin flow and registerNewUser so the
initial refresh token is generated and hashed only once, then shared with
SocialAuth.create() and the subsequent updateRefreshToken call. Remove the
duplicate token-generation path while preserving SocialAuth.create()’s required
token validation and existing behavior for both new and existing users.
- Around line 86-113: Update the SocialAuth entity used by
AuthService.reissueToken to include a JPA `@Version` field, ensuring concurrent
refresh-token updates trigger optimistic-lock conflict detection. Keep the
existing updateRefreshToken flow and map the version field with the appropriate
persistent numeric type.

In `@src/main/java/com/mr/domain/auth/service/OAuthClientService.java`:
- Around line 56-59: In both catch (Exception e) blocks of OAuthClientService,
log the original exception with log.warn using a descriptive
social-user-information lookup message, including the exception message and
stack trace, before throwing GeneralException(CommonStatus.INVALID_INPUT_VALUE).
- Around line 63-71: Update the Google response handling in OAuthClientService
to use ParameterizedTypeReference<Map<String, Object>> instead of the raw Map
type, explicitly validate that the response is non-null before reading its
fields, and switch the userinfo URI from the v2 endpoint to
https://www.googleapis.com/oauth2/v3/userinfo. Preserve the existing extraction
of id, email, and picture after validation.
🪄 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: 85f6e1f0-37e1-4282-b6e1-b5218a1f4f62

📥 Commits

Reviewing files that changed from the base of the PR and between 590a96a and e834072.

📒 Files selected for processing (12)
  • src/main/java/com/mr/domain/auth/controller/AuthController.java
  • src/main/java/com/mr/domain/auth/dto/OAuthUserInfo.java
  • src/main/java/com/mr/domain/auth/dto/req/AuthRequestDTO.java
  • src/main/java/com/mr/domain/auth/dto/res/AuthResponseDTO.java
  • src/main/java/com/mr/domain/auth/entity/SocialAuth.java
  • src/main/java/com/mr/domain/auth/entity/enums/SocialType.java
  • src/main/java/com/mr/domain/auth/repository/SocialAuthRepository.java
  • src/main/java/com/mr/domain/auth/service/AuthService.java
  • src/main/java/com/mr/domain/auth/service/OAuthClientService.java
  • src/main/java/com/mr/global/security/jwt/JwtTokenProvider.java
  • src/main/resources/application.example.yml
  • src/main/resources/application.yml

Comment thread src/main/java/com/mr/domain/auth/controller/AuthController.java
Comment thread src/main/java/com/mr/domain/auth/service/AuthService.java
Comment thread src/main/java/com/mr/domain/auth/service/OAuthClientService.java Outdated
Comment thread src/main/java/com/mr/domain/auth/service/OAuthClientService.java Outdated
Comment thread src/main/java/com/mr/domain/auth/service/AuthService.java Outdated

@ownue ownue left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

리뷰 확인 부탁드립니당....

Comment thread src/main/resources/application.example.yml Outdated
Comment thread src/main/java/com/mr/domain/auth/entity/SocialAuth.java Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

지금 소셜 계정 매핑 정보와 서비스 Refresh Token 세션 정보를 동시에 관리하고 있는 걸로 보이는데, 이 구조에서는 같은 사용자가 다른 기기에서 로그인할 때 기존 Refresh Token이 덮어써져 기존 기기의 세션이 자동으로 만료될 것 같아요! 사용자당 단일 세션 정책이 의도된 것인지 궁금합니다~

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

뮤즈리뷰가 멀티 디바이스 지원이 필요하다 생각하지 않아서 단일 세션으로 구현했는데 이부분 논의해보면 좋을 것 같습니다!

Comment thread src/main/java/com/mr/domain/auth/service/AuthService.java Outdated
Comment thread src/main/java/com/mr/domain/auth/service/AuthService.java Outdated
Comment thread src/main/java/com/mr/domain/auth/controller/AuthController.java Outdated
Comment thread src/main/java/com/mr/domain/auth/controller/AuthController.java
Comment thread src/main/java/com/mr/domain/auth/controller/AuthController.java Outdated
Comment thread src/main/java/com/mr/domain/auth/entity/SocialAuth.java
Comment thread src/main/java/com/mr/domain/auth/service/AuthService.java Outdated

@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 (3)
src/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java (1)

33-43: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

timeout이 실제로 적용되는지 검증하세요.

현재 테스트는 client가 null이 아닌지만 확인하므로, connectTimeoutreadTimeout 인자가 무시되어도 통과합니다. 지연 응답 서버를 사용해 제한 시간 초과를 검증하거나, request factory 설정을 직접 검증하는 테스트로 보강하세요.

🤖 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/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java` around
lines 33 - 43, OAuthClientServiceTest의 createOAuthRestClient_withTimeout 테스트가
RestClient 생성 여부가 아니라 connectTimeout과 readTimeout이 실제 설정되는지 검증하도록 보강하세요.
OAuthRestClientConfig.oauthRestClient에서 사용하는 request factory 설정을 직접 확인하거나 지연 응답
서버를 통해 두 제한 시간이 적용되는 동작을 검증하고, 기존 null 검증은 유지하세요.
src/test/java/com/mr/domain/auth/service/AuthServiceTest.java (1)

85-107: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

기존 Refresh Token의 재사용 실패도 검증하세요.

현재는 새 해시 저장만 확인하므로, 기존 토큰이 재발급 직후 다시 사용되더라도 실패하는지 보장하지 못합니다. 재발급 후 authService.reissueToken(originalRefreshToken)INVALID_AUTH_REQUEST로 실패하는 테스트를 추가해 Rotation 불변식을 고정하세요.

🤖 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/test/java/com/mr/domain/auth/service/AuthServiceTest.java` around lines
85 - 107, Extend reissueToken_realToken_reissuesTokensAndUpdateDbHash to call
authService.reissueToken(originalRefreshToken) again after the successful
rotation and assert that it fails with INVALID_AUTH_REQUEST, while preserving
the existing new-token and hash-update assertions.
src/main/java/com/mr/domain/auth/service/AuthService.java (1)

39-47: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

클래스 레벨 readOnly=true를 제거해 socialLogin 쓰기를 독립 트랜잭션으로 분리하세요.

AuthService에 붙은 @Transactional(readOnly = true)socialLogin에도 적용되어, TransactionTemplate.execute(...) 기본 전파가 같은 읽기 전용 트랜잭션에 참여합니다. 여기서 새로운 유저 생성과 refresh token 갱신 같은 쓰기 작업은 class-level 트랜잭션 밖에서도 안전하게 처리되도록, 클래스가 아닌 각 쓰기 작업 메서드별로 트랜잭션을 관리하는 편이 안전합니다. Spring TransactionPropagation/readOnly 문서도 함께 확인해 주세요.

🤖 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/mr/domain/auth/service/AuthService.java` around lines 39 -
47, AuthService의 클래스 레벨 `@Transactional`(readOnly = true)를 제거하고, 읽기 전용이 필요한 조회
메서드에만 명시적으로 적용하세요. socialLogin과 executeSocialLogin,
executeSocialLoginForExistingUser 등 유저 생성 및 refresh token 갱신을 수행하는 쓰기 메서드는 독립적인
쓰기 트랜잭션으로 실행되도록 각 메서드의 전파와 readOnly 설정을 조정하고, TransactionTemplate이 기존 read-only
트랜잭션에 참여하지 않게 하세요.
🤖 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/mr/domain/auth/entity/SocialAuth.java`:
- Around line 31-33: 다중 소셜 계정 연결을 지원하도록 SocialAuth와 로그인 흐름을 연결하세요. 미등록 소셜 ID를
처리할 때 무조건 새 User를 생성하지 말고, 인증된 현재 사용자를 전달받는 계정 연결 API 또는 명시된 연결 정책을 통해 해당 User에
SocialAuth를 추가하도록 변경하세요. 카카오로 가입한 사용자가 구글 로그인 시 동일 User에 연결되는 통합 테스트도 추가하세요.

In `@src/main/java/com/mr/domain/auth/service/AuthService.java`:
- Around line 81-82: SocialAuth currently stores one shared refresh-token state,
so concurrent device logins invalidate each other. In AuthService.java:81-82,
replace the existing socialAuth.updateRefreshToken flow with creation of a
separate session entity linked many-to-one to SocialAuth, storing the token
hash, expiry, and device information; apply the same new-session policy in the
registration-conflict retry path at AuthService.java:113. Add an integration
test covering two device logins where one device can independently log out and
refresh while the other remains valid.

In `@src/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java`:
- Around line 45-98: Add successful-response tests alongside
getKakaoUserInfo_nullId_throwsException and
getGoogleUserInfo_missingId_throwsException for both SocialType.KAKAO and
SocialType.GOOGLE. Mock valid provider payloads, call
OAuthClientService.getUserInfo, and assert the returned OAuthUserInfo contains
the expected social ID and profile image values.
- Around line 48-49: Update each MockRestServer expectation in
OAuthClientServiceTest at the five specified request setups to also validate the
Authorization header using the expected Bearer access token, while preserving
the existing URL matchers and response assertions.
- Around line 26-31: 각 OAuthClientService 테스트의 실행이 끝날 때 mockServer.verify()가
호출되도록 보장하세요. 테스트 메서드별로 직접 추가하거나 공통 `@AfterEach를` 사용하되,
OAuthClientService.getUserInfo()가 설정된 URL expectation을 실제로 충족했는지 검증하도록 기존 테스트
구조를 유지하세요.

---

Nitpick comments:
In `@src/main/java/com/mr/domain/auth/service/AuthService.java`:
- Around line 39-47: AuthService의 클래스 레벨 `@Transactional`(readOnly = true)를 제거하고,
읽기 전용이 필요한 조회 메서드에만 명시적으로 적용하세요. socialLogin과 executeSocialLogin,
executeSocialLoginForExistingUser 등 유저 생성 및 refresh token 갱신을 수행하는 쓰기 메서드는 독립적인
쓰기 트랜잭션으로 실행되도록 각 메서드의 전파와 readOnly 설정을 조정하고, TransactionTemplate이 기존 read-only
트랜잭션에 참여하지 않게 하세요.

In `@src/test/java/com/mr/domain/auth/service/AuthServiceTest.java`:
- Around line 85-107: Extend
reissueToken_realToken_reissuesTokensAndUpdateDbHash to call
authService.reissueToken(originalRefreshToken) again after the successful
rotation and assert that it fails with INVALID_AUTH_REQUEST, while preserving
the existing new-token and hash-update assertions.

In `@src/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java`:
- Around line 33-43: OAuthClientServiceTest의 createOAuthRestClient_withTimeout
테스트가 RestClient 생성 여부가 아니라 connectTimeout과 readTimeout이 실제 설정되는지 검증하도록 보강하세요.
OAuthRestClientConfig.oauthRestClient에서 사용하는 request factory 설정을 직접 확인하거나 지연 응답
서버를 통해 두 제한 시간이 적용되는 동작을 검증하고, 기존 null 검증은 유지하세요.
🪄 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: 48b3a008-39e9-45ac-a5b3-d1f83bc27a38

📥 Commits

Reviewing files that changed from the base of the PR and between e834072 and 2a3f24e.

📒 Files selected for processing (17)
  • src/main/java/com/mr/domain/auth/controller/AuthController.java
  • src/main/java/com/mr/domain/auth/dto/req/AuthRequestDTO.java
  • src/main/java/com/mr/domain/auth/dto/res/GoogleUserResponse.java
  • src/main/java/com/mr/domain/auth/dto/res/KakaoUserResponse.java
  • src/main/java/com/mr/domain/auth/entity/SocialAuth.java
  • src/main/java/com/mr/domain/auth/exception/AuthErrorStatus.java
  • src/main/java/com/mr/domain/auth/repository/SocialAuthRepository.java
  • src/main/java/com/mr/domain/auth/service/AuthService.java
  • src/main/java/com/mr/domain/auth/service/OAuthClientService.java
  • src/main/java/com/mr/domain/statistics/repository/UserStatisticsRepository.java
  • src/main/java/com/mr/domain/user/service/UserProfileService.java
  • src/main/java/com/mr/global/config/OAuthRestClientConfig.java
  • src/main/java/com/mr/global/security/SecurityConfig.java
  • src/main/java/com/mr/global/security/jwt/JwtTokenProvider.java
  • src/test/java/com/mr/domain/auth/service/AuthServiceTest.java
  • src/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java
  • src/test/java/com/mr/domain/user/service/UserProfileServiceTest.java

Comment thread src/main/java/com/mr/domain/auth/entity/SocialAuth.java
Comment on lines +81 to +82
} else {
socialAuth.updateRefreshToken(refreshTokenHash, expiryTime, deviceInfo);

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

기기별 세션이 아니라 소셜 계정별 단일 세션으로 동작합니다.

각 로그인은 동일한 SocialAuthrefreshTokenHash, expiredAt, deviceInfo를 덮어씁니다. 따라서 같은 제공자로 기기 B에서 로그인하면 기기 A의 Refresh Token이 즉시 무효화되어, PR의 기기별 로그아웃 계약을 충족할 수 없습니다. SocialAuth는 계정 연결만 담당하게 하고, 토큰 해시·만료·기기 정보를 별도 세션 엔티티에 다대일로 저장하세요. 두 기기 로그인 후 한 기기만 로그아웃·재발급하는 통합 테스트도 추가해 주세요.

  • src/main/java/com/mr/domain/auth/service/AuthService.java#L81-L82: 기존 로그인 시 새 기기 세션을 생성하도록 변경하세요.
  • src/main/java/com/mr/domain/auth/service/AuthService.java#L113-L113: 가입 충돌 재시도 경로도 동일한 세션 생성 정책을 사용하세요.
📍 Affects 1 file
  • src/main/java/com/mr/domain/auth/service/AuthService.java#L81-L82 (this comment)
  • src/main/java/com/mr/domain/auth/service/AuthService.java#L113-L113
🤖 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/mr/domain/auth/service/AuthService.java` around lines 81 -
82, SocialAuth currently stores one shared refresh-token state, so concurrent
device logins invalidate each other. In AuthService.java:81-82, replace the
existing socialAuth.updateRefreshToken flow with creation of a separate session
entity linked many-to-one to SocialAuth, storing the token hash, expiry, and
device information; apply the same new-session policy in the
registration-conflict retry path at AuthService.java:113. Add an integration
test covering two device logins where one device can independently log out and
refresh while the other remains valid.

@ownue ownue left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨습니다!

@on1yoneprivate on1yoneprivate left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨습니다~

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feature - 카카오/구글 소셜 로그인 API 연동 완성 및 JWT 인증 보완

4 participants