Skip to content

회원 탈퇴 및 연관 데이터 하드 삭제 구현 - #297

Merged
goder-0 merged 1 commit into
mainfrom
feature/#287-member-withdrawal
Aug 20, 2026
Merged

회원 탈퇴 및 연관 데이터 하드 삭제 구현#297
goder-0 merged 1 commit into
mainfrom
feature/#287-member-withdrawal

Conversation

@goder-0

@goder-0 goder-0 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

관련 이슈

PR 설명

변경 사항

  • 최근 발급된 access token과 명시적 확인을 요구하는 DELETE /v1/member API를 추가했습니다.
  • 탈퇴 사유는 허용된 코드값만 받고, 브라우저 client_id와 함께 하드 삭제 전에 account_delete 이벤트를 전송합니다.
  • 회원 상태를 먼저 WITHDRAWING으로 전환하고 refresh token, 요약 큐, 로그인·OAuth·토큰 재발급 및 비동기 AI 후속 처리를 차단합니다.
  • n8n의 member-delete webhook으로 회원 벡터 삭제를 시도한 뒤 FK 순서에 따라 회원 연관 데이터를 하나의 DB 트랜잭션에서 하드 삭제합니다.
  • n8n 호출 또는 Redis refresh token 삭제가 실패해도 탈퇴는 계속 처리합니다. 실패는 각각 오류 로그와 전용 메트릭으로 기록하며, Prometheus 경보는 기존 Alertmanager를 통해 Discord·이메일로 전달됩니다.
  • DB 삭제 실패 시에만 회원을 탈퇴 진행 상태로 유지해 동일 요청으로 재시도합니다. account_delete는 상태 코드를 이용해 재시도 중 중복 전송을 방지합니다.

병합 시 유의사항

#296과 docker/docker-compose.yml의 동일한 위치에 환경변수를 추가하므로 병합 충돌이 발생합니다. #296을 먼저 병합한 뒤 이 PR을 main 기준으로 rebase하고, 충돌 해결 시 다음 환경변수를 모두 유지해야 합니다.

  • ANALYTICS_GA4_DEBUG_MODE
  • APP_MEMBER_WITHDRAWAL_ENABLED
  • APP_MEMBER_WITHDRAWAL_INTERNAL_SECRET

rebase와 충돌 해결 후 전체 CI를 다시 확인합니다.

API 요청

DELETE /v1/member
Authorization: Bearer <recent-access-token>
Content-Type: application/json

{
  "confirmed": true,
  "deleteReason": "OTHER",
  "clientId": "1234567890.1234567890"
}

deleteReasonNO_USEFUL_LINKS, POOR_SEARCH, NO_REVISIT, SWITCHED_SERVICE, PRIVACY_CONCERN, OTHER 중 하나입니다.

배포 설정

  • Core: APP_MEMBER_WITHDRAWAL_ENABLED=true, APP_MEMBER_WITHDRAWAL_INTERNAL_SECRET=<shared-secret>
  • n8n: member-delete webhook에 동일한 secret을 사용하는 x-linkiving-internal-secret Header Auth를 설정합니다. webhook은 { "userId": <member-id> } 요청을 멱등 처리해야 합니다.
  • member.statusWITHDRAWING=3, WITHDRAWAL_ANALYTICS_SENT=4 코드가 추가됩니다. 운영 스키마가 enum/check constraint를 사용한다면 두 값을 허용해야 합니다.

Core 설정과 n8n Header Auth를 함께 배포한 뒤 기능을 활성화합니다. 기본값은 비활성화입니다.

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

📊 코드 커버리지 리포트

Overall Project 90.99% -2.53% 🍏
Files changed 72.06% 🍏

File Coverage
MemberStatus.java 100% 🍏
MemberDeleteReason.java 100% 🍏
MemberWithdrawalMetrics.java 100% 🍏
MemberErrorCode.java 100% 🍏
MockMemberVectorClient.java 100% 🍏
MemberService.java 96.88% -3.13% 🍏
SummaryWorker.java 95.51% -2.25% 🍏
MemberWithdrawalService.java 94.21% -5.79% 🍏
LinkSyncEventListener.java 93.06% -6.94% 🍏
MemberCommandService.java 90.77% 🍏
AuthService.java 90% -10% 🍏
MemberWithdrawalCleanupScheduler.java 89.29% -10.71% 🍏
LinkEventListener.java 88.43% -11.57%
JwtTokenProvider.java 82.41% -10.49%
MemberDataDeletionService.java 81.52% -18.48% 🍏
SummaryQueue.java 80.33% -19.67%
RagMemberVectorClient.java 79.17% -20.83% 🍏
Ga4Publisher.java 78.18% -18.18%
MemberQueryService.java 70.27% -29.73% 🍏
MemberController.java 67.59% -19.31%
JwtAuthenticationFilter.java 42.48% -28.1%

@goder-0
goder-0 force-pushed the feature/#287-member-withdrawal branch 4 times, most recently from 99f2b28 to cd742be Compare August 12, 2026 14:48
@goder-0
goder-0 marked this pull request as ready for review August 12, 2026 15:04
@goder-0
goder-0 requested a review from ckdals4600 August 12, 2026 15:05
@goder-0
goder-0 force-pushed the feature/#287-member-withdrawal branch from cd742be to 2b46a64 Compare August 12, 2026 15:21

@ckdals4600 ckdals4600 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.

탈퇴 진행 중 회원의 HTTP는 필터가 403으로 막지만, STOMP 경로는 열려 있습니다.
StompHandler의 상태 검사는 L74-77의 PENDING_TERMS뿐입니다.

아래와 탈퇴 흐름을 확인해보면,

  1. /ws/chat/**PERMIT_URLSpending/withdrawing 게이트가 있는 JwtAuthenticationFilter를 아예 안 탑니다
  2. 탈퇴 시작 전 발급된 access tokenmemberStatus 클레임은 ACTIVE입니다
  3. → WITHDRAWING 회원이 그 토큰으로 STOMP CONNECT/SEND 전부 통과 → AI 채팅 사용 가능

정상 흐름에선 이 창이 몇 초지만, DB 삭제 실패로 고착된 회원에게는 무기한입니다. "탈퇴했다"고 인식하는 사용자의 세션이 AI 비용을 계속 쓰고 새 메시지를 만들 수 있는 상태예요.

이번 취약점의 경우 낡은 토큰의 클레임이 ACTIVE 이기 때문에 클레임 검사로는 막을 수 없습니다. CONNECTgetAuthentication이 이미 DB에서 회원을 로드하니 그 결과를 통한 검사 로직 추가를 부탁드립ㄴ디ㅏ.

Authentication authentication = jwtTokenProvider.getAuthentication(token);
if (authentication.getPrincipal() instanceof CustomMemberDetail detail
        && detail.member().isWithdrawing()) {
    throw new BusinessException(MemberErrorCode.WITHDRAWAL_IN_PROGRESS);
}
accessor.setUser(authentication);

Comment thread src/main/java/com/sofa/linkiving/global/analytics/Ga4Publisher.java
Comment thread src/main/java/com/sofa/linkiving/domain/member/service/MemberQueryService.java Outdated
@goder-0
goder-0 force-pushed the feature/#287-member-withdrawal branch from 2b46a64 to 744b9a3 Compare August 18, 2026 14:10
@goder-0

goder-0 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

탈퇴 진행 중 회원의 HTTP는 필터가 403으로 막지만, STOMP 경로는 열려 있습니다. StompHandler의 상태 검사는 L74-77의 PENDING_TERMS뿐입니다.

아래와 탈퇴 흐름을 확인해보면,

  1. /ws/chat/**PERMIT_URLSpending/withdrawing 게이트가 있는 JwtAuthenticationFilter를 아예 안 탑니다
  2. 탈퇴 시작 전 발급된 access tokenmemberStatus 클레임은 ACTIVE입니다
  3. → WITHDRAWING 회원이 그 토큰으로 STOMP CONNECT/SEND 전부 통과 → AI 채팅 사용 가능

정상 흐름에선 이 창이 몇 초지만, DB 삭제 실패로 고착된 회원에게는 무기한입니다. "탈퇴했다"고 인식하는 사용자의 세션이 AI 비용을 계속 쓰고 새 메시지를 만들 수 있는 상태예요.

이번 취약점의 경우 낡은 토큰의 클레임이 ACTIVE 이기 때문에 클레임 검사로는 막을 수 없습니다. CONNECTgetAuthentication이 이미 DB에서 회원을 로드하니 그 결과를 통한 검사 로직 추가를 부탁드립ㄴ디ㅏ.

Authentication authentication = jwtTokenProvider.getAuthentication(token);
if (authentication.getPrincipal() instanceof CustomMemberDetail detail
        && detail.member().isWithdrawing()) {
    throw new BusinessException(MemberErrorCode.WITHDRAWAL_IN_PROGRESS);
}
accessor.setUser(authentication);

확인 감사합니다. CONNECT뿐 아니라 이미 연결된 세션의 SEND도 기존 ACTIVE 토큰으로 통과할 수 있어, 두 경로 모두 getAuthentication()을 통해 DB의 최신 회원 상태를 확인하도록 수정했습니다. WITHDRAWING, WITHDRAWAL_ANALYTICS_SENT 상태는 차단하며 CONNECT/SEND 각각에 대한 테스트도 추가했습니다.

@ckdals4600 ckdals4600 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.

전부 확인했습니다.
LGTM, approve 합니다.
충돌 해결 후 머지 진행하시면 될 것 같습니다.

@goder-0
goder-0 force-pushed the feature/#287-member-withdrawal branch from 744b9a3 to 4031d95 Compare August 18, 2026 14:39
@goder-0
goder-0 merged commit 4725c6d into main Aug 20, 2026
3 checks passed
@goder-0
goder-0 deleted the feature/#287-member-withdrawal branch August 20, 2026 15:02
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.

회원 탈퇴 기능 구현

2 participants