Skip to content

feat: 회원 탈퇴 시 소셜 연동 해제(unlink) 지원#99

Merged
buddle031 merged 7 commits into
mainfrom
feat/account-withdrawal-provider-unlink
Jun 27, 2026
Merged

feat: 회원 탈퇴 시 소셜 연동 해제(unlink) 지원#99
buddle031 merged 7 commits into
mainfrom
feat/account-withdrawal-provider-unlink

Conversation

@buddle031

@buddle031 buddle031 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

✨ 작업 내용 한 줄 요약

  • 회원 탈퇴 시 OAuth Provider와의 외부 연동 해제 및 Provider Token 저장 기능을 추가했습니다.

🛠️ 작업 내용

  • OAuth 로그인 성공 시 Provider Access/Refresh Token을 Redis에 저장하도록 추가
  • 회원 탈퇴 시 Google / Naver / Kakao Provider unlink(revoke) 수행
  • Provider unlink 완료 후 내부 탈퇴 처리(소셜 계정 삭제, Refresh Token 삭제 등) 수행
  • Provider Token 저장 시 TTL 미적용
  • Provider Token 또는 unlink client가 없는 경우 내부 탈퇴가 실패하지 않도록 예외 처리 보완

🧠 기술적 의사결정

  • 기존 가입자는 Provider Token이 존재하지 않을 수 있으므로, Provider Token이 없는 경우 외부 unlink는 건너뛰고 내부 탈퇴는 정상 진행하도록 처리했습니다.
  • OAuth Provider Token은 탈퇴 시 외부 revoke/unlink 수행을 위해 로그인 시 Redis에 저장하도록 설계했습니다.
  • Provider Token은 회원 탈퇴 시 함께 삭제하여 불필요하게 남지 않도록 했습니다

📚 참고 자료 (선택)

📷 스크린샷 (선택)

Summary by CodeRabbit

  • New Features
    • OAuth2 로그인 성공 시 소셜 제공자 토큰을 저장합니다.
    • 사용자별 소셜 계정 연동 해제 기능이 추가되어 Google/Kakao/Naver 연동 해제가 지원됩니다.
  • Bug Fixes
    • 계정 탈퇴 시 소셜 연동 해제 및 토큰 정리의 처리 순서가 개선되었습니다.
    • 소셜 제공자 토큰까지 함께 삭제되도록 범위가 확장되었습니다.
  • Tests
    • OAuth2 성공 핸들러 테스트가 토큰 저장 로직 변경 사항을 반영하도록 업데이트되었습니다.

@buddle031 buddle031 requested a review from mike7643 as a code owner June 26, 2026 02:36
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 014a0c56-20a1-4801-8c23-d839830df13f

📥 Commits

Reviewing files that changed from the base of the PR and between 7ae7880 and cf784b3.

📒 Files selected for processing (1)
  • src/main/java/com/zimdugo/auth/application/OAuth2ProviderTokenService.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/zimdugo/auth/application/OAuth2ProviderTokenService.java

📝 Walkthrough

Walkthrough

OAuth2 로그인 성공 시 제공자 토큰을 저장하고, 탈퇴 시 저장된 토큰으로 소셜 연동 해제를 먼저 수행한 뒤 소셜 계정과 제공자 토큰을 삭제하도록 흐름이 확장됐다.

Changes

소셜 토큰 저장과 탈퇴 정리

Layer / File(s) Summary
토큰 계약과 Redis 저장소
src/main/java/com/zimdugo/auth/domain/SocialProviderToken.java, src/main/java/com/zimdugo/auth/domain/SocialProviderTokenRepository.java, src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java
SocialProviderToken 레코드와 저장소 계약이 추가되고, Redis 구현이 저장·조회·사용자별 삭제를 JSON 직렬화로 처리한다.
OAuth2 성공 시 토큰 저장
src/main/java/com/zimdugo/auth/application/OAuth2ProviderTokenService.java, src/main/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandler.java, src/test/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandlerTest.java
OAuth2SuccessHandlerOAuth2AuthorizedClient에서 access/refresh 토큰을 읽어 SocialProviderTokenRepository에 저장하고, 성공/실패 메시지와 처리 순서를 조정한다.
연동 해제 서비스
src/main/java/com/zimdugo/auth/domain/SocialAccountUnlinkClient.java, src/main/java/com/zimdugo/auth/application/SocialAccountUnlinkService.java
SocialAccountUnlinkService가 provider별 클라이언트를 매핑해 저장된 토큰을 조회하고, 연동 해제 가능 여부를 판단해 호출한다.
소셜 제공자 연동 해제 클라이언트
src/main/java/com/zimdugo/auth/infrastructure/client/GoogleSocialAccountUnlinkClient.java, src/main/java/com/zimdugo/auth/infrastructure/client/KakaoSocialAccountUnlinkClient.java, src/main/java/com/zimdugo/auth/infrastructure/client/NaverSocialAccountUnlinkClient.java
Google, Kakao, Naver 클라이언트가 각 외부 OAuth API로 unlink 또는 revoke 요청을 보내고 토큰 갱신과 오류 변환을 수행한다.
탈퇴 정리 순서
src/main/java/com/zimdugo/auth/application/AccountWithdrawalService.java
withdraw가 언아노나이즈와 사용자 저장 전에 unlinkAll(userId)를 호출하고, 이후 소셜 계정과 제공자 토큰을 삭제한다.

Sequence Diagram(s)

OAuth2 성공 시 제공자 토큰 저장

sequenceDiagram
  participant OAuth2SuccessHandler
  participant OAuth2ProviderTokenService
  participant OAuth2AuthorizedClientService
  participant SocialProviderTokenRepository

  OAuth2SuccessHandler->>OAuth2ProviderTokenService: saveProviderToken(authentication, userId)
  OAuth2ProviderTokenService->>OAuth2AuthorizedClientService: loadAuthorizedClient(registrationId, principalName)
  OAuth2AuthorizedClientService-->>OAuth2ProviderTokenService: OAuth2AuthorizedClient
  OAuth2ProviderTokenService->>SocialProviderTokenRepository: save(userId, provider, SocialProviderToken)
Loading

탈퇴 시 소셜 연동 해제와 정리

sequenceDiagram
  participant AccountWithdrawalService
  participant SocialAccountUnlinkService
  participant SocialAccountReader
  participant SocialProviderTokenRepository
  participant SocialAccountUnlinkClient

  AccountWithdrawalService->>SocialAccountUnlinkService: unlinkAll(userId)
  SocialAccountUnlinkService->>SocialAccountReader: findAllByUserId(userId)
  loop each SocialAccount
    SocialAccountUnlinkService->>SocialProviderTokenRepository: find(userId, provider)
    alt client and token exist
      SocialAccountUnlinkService->>SocialAccountUnlinkClient: unlink(socialAccount, token)
      SocialAccountUnlinkClient-->>SocialAccountUnlinkService: completed
    else missing client or token
      SocialAccountUnlinkService-->>SocialAccountUnlinkService: skip
    end
  end
  AccountWithdrawalService->>SocialProviderTokenRepository: deleteAllByUserId(userId)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mike7643

Poem

나는 토끼, 깡총깡총 로그인했지 🐰
토큰을 품고 Redis 들판을 건너
연동은 풀리고, 발자국은 정리되고
바람은 조용히 탈퇴 길을 닦아
아침 햇살처럼 새로 깨어났어

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR의 핵심 변경인 회원 탈퇴 시 OAuth 소셜 연동 해제 지원을 정확히 요약합니다.
Description check ✅ Passed 요구된 한 줄 요약, 작업 내용, 기술적 의사결정 섹션이 모두 포함되어 있어 템플릿을 대부분 충족합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/account-withdrawal-provider-unlink

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

🧹 Nitpick comments (1)
src/main/java/com/zimdugo/auth/application/SocialAccountUnlinkService.java (1)

30-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

동일 provider 클라이언트를 조용히 덮어쓰지 마세요.

같은 AuthProvider 구현체가 둘 이상 주입되면 마지막 빈이 앞선 빈을 덮어써서 잘못된 unlink 클라이언트가 선택될 수 있습니다. put()의 반환값을 확인해서 중복 등록 시 바로 실패시키는 편이 안전합니다.

🤖 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/zimdugo/auth/application/SocialAccountUnlinkService.java`
around lines 30 - 33, The SocialAccountUnlinkService constructor currently
builds the unlinkClients map by calling put() for each
SocialAccountUnlinkClient, which can silently overwrite an existing entry when
the same AuthProvider appears more than once. Update the constructor logic to
detect duplicate provider registration while iterating over unlinkClients, using
the put() return value in SocialAccountUnlinkService and failing immediately
when a provider is already present so the wrong client cannot be selected.
🤖 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/zimdugo/auth/application/AccountWithdrawalService.java`:
- Around line 47-53: Move the side-effecting cleanup in AccountWithdrawalService
out of the DB transaction: unlinkAll() on socialAccountUnlinkService and
deleteAllByUserId() on socialProviderTokenRepository should run only after the
userStore/store and socialAccountStore changes are successfully committed.
Refactor the withdrawal flow so the transactional part only persists the
anonymized user and local DB state, then trigger the external unlink and
Redis/token cleanup via an after-commit event or outbox-based handler.

In `@src/main/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandler.java`:
- Line 70: The provider token persistence in OAuth2 success flow should not
block login completion when it fails. Update the OAuth2 success handler around
saveProviderToken so failures from loadAuthorizedClient, AuthProvider.valueOf,
or Redis storage are caught and logged as warn, then continue with session
creation and the rest of the success path. Keep the failure isolated inside the
token-saving path in OAuth2SuccessHandler so missing provider tokens do not stop
authentication success.

In
`@src/main/java/com/zimdugo/auth/infrastructure/client/GoogleSocialAccountUnlinkClient.java`:
- Around line 32-38: The Google social unlink revocation request is sending the
token in the URL query string via GoogleSocialAccountUnlinkClient and should
instead send it in the POST body. Update the revoke call in the client to submit
`token` as `application/x-www-form-urlencoded` content rather than using
`uriBuilder.queryParam`, keeping the `/revoke` endpoint and the token resolution
logic in `resolveRevocationToken` intact.

In
`@src/main/java/com/zimdugo/auth/infrastructure/client/KakaoSocialAccountUnlinkClient.java`:
- Around line 49-59: 외부 unlink 실패가 `SocialAccountUnlinkService`의 내부 탈퇴 흐름을 중단시키고
있습니다. `KakaoSocialAccountUnlinkClient.unlink(...)`에서 던지는 `BusinessException`을
호출자에서 그대로 전파하지 말고, provider별 호출부에서 try/catch로 격리해 실패해도 사용자 익명화, 소셜 계정 삭제,
provider token 삭제는 계속 진행되도록 `SocialAccountUnlinkService`와 관련 호출 흐름을 수정하세요.
`unlinkClient.unlink(...)`, `SocialAccountUnlinkService`,
`KakaoSocialAccountUnlinkClient.unlink(...)`를 기준으로 실패를 기록만 하고 다음 단계로 넘어가거나, 실패
결과를 반환하는 계약으로 변경해 내부 탈퇴가 중단되지 않게 하세요.

In
`@src/main/java/com/zimdugo/auth/infrastructure/client/NaverSocialAccountUnlinkClient.java`:
- Around line 43-59: 외부 unlink 실패가 내부 탈퇴를 막고 있으므로, SocialAccountUnlinkService에서
unlinkClient.unlink(...) 호출을 provider별로 try/catch로 감싸 BusinessException이 전파되지 않게
격리하세요. NaverSocialAccountUnlinkClient.unlink에서 발생하는 실패는 로그만 남기고, 이어서 사용자 익명화, 소셜
계정 삭제, provider token 삭제가 계속 실행되도록 흐름을 유지해야 합니다. SocialAccountUnlinkService의 각
provider 분기와 unlinkClient, BusinessException 처리 지점을 함께 수정해 주세요.
- Around line 45-52: The Naver unlink/token request currently exposes sensitive
credentials by putting client_secret and tokens into the URL query in
NaverSocialAccountUnlinkClient. Update the request-building logic in the
unlink/token flow to send client_secret, access_token, and refresh_token in an
application/x-www-form-urlencoded POST body instead of uriBuilder.queryParam,
keeping the existing request methods and token-resolving helpers but moving the
payload construction into the body handling.

In
`@src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java`:
- Around line 25-28: The RedisSocialProviderTokenRepository.save method is
storing SocialProviderToken without any retention limit, so provider credentials
can remain indefinitely. Update save to apply an appropriate TTL when calling
stringRedisTemplate.opsForValue().set for the key(userId, provider), or encrypt
the serialized token before persisting if TTL cannot be used. Keep the fix
localized to RedisSocialProviderTokenRepository and its save/key/serialize flow.
- Around line 39-46: The deleteAllByUserId method in
RedisSocialProviderTokenRepository currently uses stringRedisTemplate.keys(),
which blocks Redis on large keyspaces; replace it with a cursor-based SCAN flow
that iterates matching keys by KEY_PREFIX + userId + ":*" and deletes them in
batches. Apply the same pattern in RedisRefreshTokenRepository as well so both
repositories avoid KEYS and use non-blocking key iteration.

---

Nitpick comments:
In `@src/main/java/com/zimdugo/auth/application/SocialAccountUnlinkService.java`:
- Around line 30-33: The SocialAccountUnlinkService constructor currently builds
the unlinkClients map by calling put() for each SocialAccountUnlinkClient, which
can silently overwrite an existing entry when the same AuthProvider appears more
than once. Update the constructor logic to detect duplicate provider
registration while iterating over unlinkClients, using the put() return value in
SocialAccountUnlinkService and failing immediately when a provider is already
present so the wrong client cannot be selected.
🪄 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

Run ID: 450bf4e4-4f4e-4909-8da4-88e9754e9069

📥 Commits

Reviewing files that changed from the base of the PR and between f6522ad and 95d64c4.

📒 Files selected for processing (10)
  • src/main/java/com/zimdugo/auth/application/AccountWithdrawalService.java
  • src/main/java/com/zimdugo/auth/application/SocialAccountUnlinkService.java
  • src/main/java/com/zimdugo/auth/domain/SocialAccountUnlinkClient.java
  • src/main/java/com/zimdugo/auth/domain/SocialProviderToken.java
  • src/main/java/com/zimdugo/auth/domain/SocialProviderTokenRepository.java
  • src/main/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandler.java
  • src/main/java/com/zimdugo/auth/infrastructure/client/GoogleSocialAccountUnlinkClient.java
  • src/main/java/com/zimdugo/auth/infrastructure/client/KakaoSocialAccountUnlinkClient.java
  • src/main/java/com/zimdugo/auth/infrastructure/client/NaverSocialAccountUnlinkClient.java
  • src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java

Comment on lines +47 to +53
socialAccountUnlinkService.unlinkAll(userId);

user.anonymizeForWithdrawal();
userStore.store(user);

socialAccountStore.deleteAllByUserId(userId);
socialProviderTokenRepository.deleteAllByUserId(userId);

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

외부 unlink와 Redis 삭제를 DB 트랜잭션 안에서 실행하면 부분 실패가 남습니다.

unlinkAll()은 외부 side effect이고 socialProviderTokenRepository.deleteAllByUserId()도 DB 트랜잭션에 묶이지 않는 삭제입니다. 지금 순서에서는 뒤쪽 userStore/socialAccountStore/커밋이 실패해도 외부 연동 해제나 Redis 토큰 삭제만 먼저 확정될 수 있어서, 내부 탈퇴 상태와 외부 연동 상태가 어긋납니다. 이 단계는 after-commit 이벤트나 아웃박스처럼 커밋 이후에 실행되도록 분리하는 편이 안전합니다.

🤖 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/zimdugo/auth/application/AccountWithdrawalService.java`
around lines 47 - 53, Move the side-effecting cleanup in
AccountWithdrawalService out of the DB transaction: unlinkAll() on
socialAccountUnlinkService and deleteAllByUserId() on
socialProviderTokenRepository should run only after the userStore/store and
socialAccountStore changes are successfully committed. Refactor the withdrawal
flow so the transactional part only persists the anonymized user and local DB
state, then trigger the external unlink and Redis/token cleanup via an
after-commit event or outbox-based handler.

Comment thread src/main/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandler.java Outdated
Comment on lines +49 to +59
public void unlink(SocialAccount socialAccount, SocialProviderToken token) {
try {
apiRestClient.post()
.uri("/v1/user/unlink")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + resolveAccessToken(token))
.retrieve()
.toBodilessEntity();
} catch (RestClientException exception) {
log.error("카카오 연동 해제에 실패했습니다. userId={}", socialAccount.getUser().getId(), exception);
throw new BusinessException(ErrorCode.EXTERNAL_API_ERROR, exception);
}

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 | 🟠 Major | ⚡ Quick win

외부 unlink 실패가 내부 탈퇴를 중단하지 않게 격리하세요.

제공된 SocialAccountUnlinkService 흐름은 unlinkClient.unlink(...) 예외를 잡지 않으므로, Line 58/67/84의 BusinessException이 전파되면 사용자 익명화, 소셜 계정 삭제, provider token 삭제가 실행되지 않습니다. PR 목표처럼 내부 탈퇴는 계속되어야 한다면 caller에서 provider별 try/catch로 계속 진행시키거나 실패 결과를 반환하는 계약으로 바꿔 주세요.

Also applies to: 62-87

🧰 Tools
🪛 PMD (7.25.0)

[Low] 57-57: InvalidLogMessageFormat (Error Prone): Too many arguments, expected 1 argument but found 2

(InvalidLogMessageFormat (Error Prone))

🤖 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/zimdugo/auth/infrastructure/client/KakaoSocialAccountUnlinkClient.java`
around lines 49 - 59, 외부 unlink 실패가 `SocialAccountUnlinkService`의 내부 탈퇴 흐름을
중단시키고 있습니다. `KakaoSocialAccountUnlinkClient.unlink(...)`에서 던지는
`BusinessException`을 호출자에서 그대로 전파하지 말고, provider별 호출부에서 try/catch로 격리해 실패해도 사용자
익명화, 소셜 계정 삭제, provider token 삭제는 계속 진행되도록 `SocialAccountUnlinkService`와 관련 호출
흐름을 수정하세요. `unlinkClient.unlink(...)`, `SocialAccountUnlinkService`,
`KakaoSocialAccountUnlinkClient.unlink(...)`를 기준으로 실패를 기록만 하고 다음 단계로 넘어가거나, 실패
결과를 반환하는 계약으로 변경해 내부 탈퇴가 중단되지 않게 하세요.

Comment on lines +43 to +59
public void unlink(SocialAccount socialAccount, SocialProviderToken token) {
try {
restClient.get()
.uri(uriBuilder -> uriBuilder
.path("/oauth2.0/token")
.queryParam("grant_type", "delete")
.queryParam("client_id", clientId)
.queryParam("client_secret", clientSecret)
.queryParam("access_token", resolveAccessToken(token))
.queryParam("service_provider", "NAVER")
.build())
.retrieve()
.body(NaverTokenResponse.class);
} catch (RestClientException exception) {
log.error("네이버 연동 해제에 실패했습니다. userId={}", socialAccount.getUser().getId(), exception);
throw new BusinessException(ErrorCode.EXTERNAL_API_ERROR, exception);
}

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 | 🟠 Major | ⚡ Quick win

외부 unlink 실패가 내부 탈퇴를 중단하지 않게 격리하세요.

제공된 SocialAccountUnlinkService 흐름은 unlinkClient.unlink(...) 예외를 잡지 않으므로, Line 58/67/82의 BusinessException이 전파되면 사용자 익명화, 소셜 계정 삭제, provider token 삭제가 실행되지 않습니다. 내부 탈퇴는 계속 진행되도록 caller에서 provider별 try/catch로 격리해 주세요.

Also applies to: 66-82

🧰 Tools
🪛 PMD (7.25.0)

[Low] 57-57: InvalidLogMessageFormat (Error Prone): Too many arguments, expected 1 argument but found 2

(InvalidLogMessageFormat (Error Prone))

🤖 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/zimdugo/auth/infrastructure/client/NaverSocialAccountUnlinkClient.java`
around lines 43 - 59, 외부 unlink 실패가 내부 탈퇴를 막고 있으므로, SocialAccountUnlinkService에서
unlinkClient.unlink(...) 호출을 provider별로 try/catch로 감싸 BusinessException이 전파되지 않게
격리하세요. NaverSocialAccountUnlinkClient.unlink에서 발생하는 실패는 로그만 남기고, 이어서 사용자 익명화, 소셜
계정 삭제, provider token 삭제가 계속 실행되도록 흐름을 유지해야 합니다. SocialAccountUnlinkService의 각
provider 분기와 unlinkClient, BusinessException 처리 지점을 함께 수정해 주세요.

Comment on lines +25 to +28
@Override
public void save(Long userId, AuthProvider provider, SocialProviderToken token) {
stringRedisTemplate.opsForValue().set(key(userId, provider), serialize(token));
}

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

Provider 토큰에 보존 한계를 두세요.

Line 27은 access/refresh token을 TTL 없이 JSON으로 저장합니다. 탈퇴 전까지 장기 휴면 계정의 bearer credential이 계속 남을 수 있으니, provider refresh token 유효기간/보존 정책에 맞춘 TTL 또는 저장 전 암호화를 적용해 주세요.

🤖 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/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java`
around lines 25 - 28, The RedisSocialProviderTokenRepository.save method is
storing SocialProviderToken without any retention limit, so provider credentials
can remain indefinitely. Update save to apply an appropriate TTL when calling
stringRedisTemplate.opsForValue().set for the key(userId, provider), or encrypt
the serialized token before persisting if TTL cannot be used. Keep the fix
localized to RedisSocialProviderTokenRepository and its save/key/serialize flow.

Comment on lines +39 to +46
@Override
public void deleteAllByUserId(Long userId) {
Set<String> keys = stringRedisTemplate.keys(KEY_PREFIX + userId + ":*");
if (keys == null || keys.isEmpty()) {
return;
}
stringRedisTemplate.delete(keys);
}

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2597


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2597


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2597


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2597


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2597


🏁 Script executed:

rg -n "deleteAllByUserId\\(" -S src/main/java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 1612


🏁 Script executed:

rg -n "deleteAllByUserId\\(" -S src/main/java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 1612


🏁 Script executed:

rg -n "deleteAllByUserId\\(" -S src/main/java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 1612


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisRefreshTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2604


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisRefreshTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2604


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisRefreshTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2604


🏁 Script executed:

sed -n '1,220p' src/main/java/com/zimdugo/auth/infrastructure/persistence/RedisRefreshTokenRepository.java

Repository: JECT-Study/Zimdugo-4th-Server

Length of output: 2604


KEYS 대신 cursor 기반 SCAN으로 바꾸세요.
stringRedisTemplate.keys(...)는 Redis 전체 keyspace를 블로킹하므로, 탈퇴 요청이 많거나 키가 늘면 응답 지연을 유발할 수 있습니다. 같은 패턴이 RedisRefreshTokenRepository에도 있으니 함께 교체하세요.

🤖 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/zimdugo/auth/infrastructure/persistence/RedisSocialProviderTokenRepository.java`
around lines 39 - 46, The deleteAllByUserId method in
RedisSocialProviderTokenRepository currently uses stringRedisTemplate.keys(),
which blocks Redis on large keyspaces; replace it with a cursor-based SCAN flow
that iterates matching keys by KEY_PREFIX + userId + ":*" and deletes them in
batches. Apply the same pattern in RedisRefreshTokenRepository as well so both
repositories avoid KEYS and use non-blocking key iteration.

@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: 1

🧹 Nitpick comments (1)
src/test/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandlerTest.java (1)

51-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

saveProviderToken 호출 검증을 추가하면 좋습니다.

새로 주입한 providerTokenService가 mock으로만 전달되고 실제 호출 여부는 검증되지 않습니다. 토큰 저장이 성공 경로에서 호출되는지 확인하는 verify(providerTokenService).saveProviderToken(any(), eq(42L));를 추가하면 회귀를 방지할 수 있습니다.

🤖 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/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandlerTest.java`
around lines 51 - 61, The success-path test for OAuth2 authentication is missing
verification that the newly injected providerTokenService is actually used.
Update OAuth2SuccessHandlerTest to assert that
providerTokenService.saveProviderToken is invoked during
handler.onAuthenticationSuccess, alongside the existing sessionService and
callbackUrlCookieManager checks, so the test covers the token-save behavior for
the authenticated principal.
🤖 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/zimdugo/auth/application/OAuth2ProviderTokenService.java`:
- Around line 43-51: `OAuth2ProviderTokenService`에서
`AuthProvider.valueOf(registrationId.toUpperCase())`가 알 수 없는 registrationId에 대해
예외를 던져 로그인 흐름을 중단할 수 있습니다. `save` 호출 직전에 `registrationId`를 안전하게 검증하거나 enum 매칭
실패를 처리해, 매칭되지 않으면 토큰 저장만 건너뛰고 예외를 밖으로 전파하지 않도록 `save`, `AuthProvider.valueOf`,
`OAuth2ProviderTokenService` 흐름을 수정하세요.

---

Nitpick comments:
In
`@src/test/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandlerTest.java`:
- Around line 51-61: The success-path test for OAuth2 authentication is missing
verification that the newly injected providerTokenService is actually used.
Update OAuth2SuccessHandlerTest to assert that
providerTokenService.saveProviderToken is invoked during
handler.onAuthenticationSuccess, alongside the existing sessionService and
callbackUrlCookieManager checks, so the test covers the token-save behavior for
the authenticated principal.
🪄 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

Run ID: a18ba064-f463-45a2-bc54-492c671db427

📥 Commits

Reviewing files that changed from the base of the PR and between 95d64c4 and 7ae7880.

📒 Files selected for processing (6)
  • src/main/java/com/zimdugo/auth/application/OAuth2ProviderTokenService.java
  • src/main/java/com/zimdugo/auth/application/SocialAccountUnlinkService.java
  • src/main/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandler.java
  • src/main/java/com/zimdugo/auth/infrastructure/client/GoogleSocialAccountUnlinkClient.java
  • src/main/java/com/zimdugo/auth/infrastructure/client/NaverSocialAccountUnlinkClient.java
  • src/test/java/com/zimdugo/auth/entrypoint/oauth2/OAuth2SuccessHandlerTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/zimdugo/auth/infrastructure/client/GoogleSocialAccountUnlinkClient.java
  • src/main/java/com/zimdugo/auth/application/SocialAccountUnlinkService.java
  • src/main/java/com/zimdugo/auth/infrastructure/client/NaverSocialAccountUnlinkClient.java

@buddle031 buddle031 merged commit 78eaaf2 into main Jun 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant