[FEAT] 소셜 로그인 및 인증 api 구현 - #61
Conversation
|
Warning Review limit reached
Next review available in: 35 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughOAuth 제공자 사용자 조회, 소셜 인증 정보와 토큰 상태 저장, JWT 재발급·로그아웃·탈퇴 처리가 구현되었습니다. 인증 DTO와 엔드포인트가 확장되었고, OAuth 오류 매핑 및 관련 통합 테스트가 추가되었습니다. Changes소셜 로그인 및 JWT 인증 흐름
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
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
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 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: 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 winGoogle 응답 처리: raw
Map타입, null 체크 부재, 구식 엔드포인트.세 가지가 한 곳에 모여 있습니다.
Map response는 raw type이라 unchecked 캐스팅 경고가 발생합니다. Kakao 쪽처럼ParameterizedTypeReference<Map<String, Object>>를 사용해주세요.response가null인 경우에 대한 명시적 체크가 없어response.get("id")에서 NPE가 날 수 있습니다(현재는 바깥catch(Exception e)가 감싸서 죽지는 않지만, 원인 파악이 더 어려워집니다).- 사용 중인
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
TokenInfo와TokenResponse가 완전히 동일한 구조예요 — 하나로 합쳐주세요.두 레코드 모두
accessToken/refreshToken/accessTokenExpiresInSeconds필드가 똑같습니다.LoginResponse.tokenInfo필드는TokenResponse타입인데 정작TokenInfo라는 별도 타입이 존재해서, 나중에 합류하는 개발자가 "둘이 뭐가 다르지?" 하며 혼란스러워할 확률이 높습니다. 하나의 레코드로 통일해서LoginResponse와reissueToken응답 모두 같은 타입을 재사용하는 걸 권장드립니다.♻️ 제안: `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
📒 Files selected for processing (12)
src/main/java/com/mr/domain/auth/controller/AuthController.javasrc/main/java/com/mr/domain/auth/dto/OAuthUserInfo.javasrc/main/java/com/mr/domain/auth/dto/req/AuthRequestDTO.javasrc/main/java/com/mr/domain/auth/dto/res/AuthResponseDTO.javasrc/main/java/com/mr/domain/auth/entity/SocialAuth.javasrc/main/java/com/mr/domain/auth/entity/enums/SocialType.javasrc/main/java/com/mr/domain/auth/repository/SocialAuthRepository.javasrc/main/java/com/mr/domain/auth/service/AuthService.javasrc/main/java/com/mr/domain/auth/service/OAuthClientService.javasrc/main/java/com/mr/global/security/jwt/JwtTokenProvider.javasrc/main/resources/application.example.ymlsrc/main/resources/application.yml
There was a problem hiding this comment.
지금 소셜 계정 매핑 정보와 서비스 Refresh Token 세션 정보를 동시에 관리하고 있는 걸로 보이는데, 이 구조에서는 같은 사용자가 다른 기기에서 로그인할 때 기존 Refresh Token이 덮어써져 기존 기기의 세션이 자동으로 만료될 것 같아요! 사용자당 단일 세션 정책이 의도된 것인지 궁금합니다~
There was a problem hiding this comment.
뮤즈리뷰가 멀티 디바이스 지원이 필요하다 생각하지 않아서 단일 세션으로 구현했는데 이부분 논의해보면 좋을 것 같습니다!
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.java (1)
33-43: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick wintimeout이 실제로 적용되는지 검증하세요.
현재 테스트는
client가 null이 아닌지만 확인하므로,connectTimeout과readTimeout인자가 무시되어도 통과합니다. 지연 응답 서버를 사용해 제한 시간 초과를 검증하거나, 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
📒 Files selected for processing (17)
src/main/java/com/mr/domain/auth/controller/AuthController.javasrc/main/java/com/mr/domain/auth/dto/req/AuthRequestDTO.javasrc/main/java/com/mr/domain/auth/dto/res/GoogleUserResponse.javasrc/main/java/com/mr/domain/auth/dto/res/KakaoUserResponse.javasrc/main/java/com/mr/domain/auth/entity/SocialAuth.javasrc/main/java/com/mr/domain/auth/exception/AuthErrorStatus.javasrc/main/java/com/mr/domain/auth/repository/SocialAuthRepository.javasrc/main/java/com/mr/domain/auth/service/AuthService.javasrc/main/java/com/mr/domain/auth/service/OAuthClientService.javasrc/main/java/com/mr/domain/statistics/repository/UserStatisticsRepository.javasrc/main/java/com/mr/domain/user/service/UserProfileService.javasrc/main/java/com/mr/global/config/OAuthRestClientConfig.javasrc/main/java/com/mr/global/security/SecurityConfig.javasrc/main/java/com/mr/global/security/jwt/JwtTokenProvider.javasrc/test/java/com/mr/domain/auth/service/AuthServiceTest.javasrc/test/java/com/mr/domain/auth/service/OAuthClientServiceTest.javasrc/test/java/com/mr/domain/user/service/UserProfileServiceTest.java
| } else { | ||
| socialAuth.updateRefreshToken(refreshTokenHash, expiryTime, deviceInfo); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
기기별 세션이 아니라 소셜 계정별 단일 세션으로 동작합니다.
각 로그인은 동일한 SocialAuth의 refreshTokenHash, 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.
📍 개요
⛓️💥 관련 이슈
🛠️ 작업 내용
OAuthClientService연동🔥 리뷰 요청 사항
✅ 체크리스트
📎 참고 사항
Summary by CodeRabbit
새로운 기능
개선 사항