diff --git a/src/main/java/umc/cockple/demo/domain/member/controller/MemberController.java b/src/main/java/umc/cockple/demo/domain/member/controller/MemberController.java index 9f2d6bb3..7e4bd68b 100644 --- a/src/main/java/umc/cockple/demo/domain/member/controller/MemberController.java +++ b/src/main/java/umc/cockple/demo/domain/member/controller/MemberController.java @@ -7,10 +7,10 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import umc.cockple.demo.domain.member.dto.*; -import umc.cockple.demo.domain.member.dto.kakao.KakaoLoginDTO; import umc.cockple.demo.domain.member.exception.MemberErrorCode; import umc.cockple.demo.domain.member.exception.MemberException; import umc.cockple.demo.domain.member.service.MemberCommandService; @@ -19,6 +19,7 @@ import umc.cockple.demo.global.oauth2.service.KakaoOauthService; import umc.cockple.demo.global.response.BaseResponse; import umc.cockple.demo.global.response.code.status.CommonSuccessCode; +import umc.cockple.demo.global.security.domain.CustomUserDetails; import umc.cockple.demo.global.security.utils.SecurityUtil; import java.io.IOException; @@ -128,9 +129,9 @@ public ResponseEntity refresh(@CookieValue("refreshToken") @PatchMapping(value = "/member") @Operation(summary = "회원 탈퇴 API", description = "사용자 회원 탈퇴") - public BaseResponse withdraw() { + public BaseResponse withdraw(@AuthenticationPrincipal CustomUserDetails member) { - Long memberId = SecurityUtil.getCurrentMemberId(); + Long memberId = member.getMemberId(); memberCommandService.withdrawMember(memberId); return BaseResponse.success(CommonSuccessCode.NO_CONTENT); diff --git a/src/main/java/umc/cockple/demo/domain/member/exception/MemberErrorCode.java b/src/main/java/umc/cockple/demo/domain/member/exception/MemberErrorCode.java index 6986a37f..e0333069 100644 --- a/src/main/java/umc/cockple/demo/domain/member/exception/MemberErrorCode.java +++ b/src/main/java/umc/cockple/demo/domain/member/exception/MemberErrorCode.java @@ -43,6 +43,8 @@ public enum MemberErrorCode implements BaseErrorCode { NICKNAME_IS_NULL(HttpStatus.BAD_REQUEST, "MEM_AUTH_401", "토큰 생성 중 닉네임이 존재하지 않습니다."), + OAUTH_UNLINK_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "MEM_AUTH_501", "카카오 연결 끊기에 실패했습니다.") + ; diff --git a/src/main/java/umc/cockple/demo/domain/member/service/MemberCommandService.java b/src/main/java/umc/cockple/demo/domain/member/service/MemberCommandService.java index 2cfbcde5..5a62d8c2 100644 --- a/src/main/java/umc/cockple/demo/domain/member/service/MemberCommandService.java +++ b/src/main/java/umc/cockple/demo/domain/member/service/MemberCommandService.java @@ -10,7 +10,6 @@ import umc.cockple.demo.domain.member.domain.*; import umc.cockple.demo.domain.member.dto.MemberDetailInfoRequestDTO; import umc.cockple.demo.domain.member.dto.UpdateProfileRequestDTO; -import umc.cockple.demo.domain.member.dto.kakao.KakaoLoginDTO; import umc.cockple.demo.domain.member.enums.MemberPartyStatus; import umc.cockple.demo.domain.member.exception.MemberErrorCode; import umc.cockple.demo.domain.member.exception.MemberException; @@ -19,9 +18,9 @@ import umc.cockple.demo.domain.image.service.ImageService; import java.util.List; +import umc.cockple.demo.global.oauth2.service.KakaoOauthService; import static umc.cockple.demo.domain.member.dto.CreateMemberAddrDTO.*; -import static umc.cockple.demo.domain.member.dto.kakao.KakaoLoginDTO.*; @Service @Transactional @@ -36,6 +35,7 @@ public class MemberCommandService { private final MemberPartyRepository memberPartyRepository; private final ChatRoomMemberRepository chatRoomMemberRepository; + private final KakaoOauthService kakaoOauthService; private final ImageService imageService; @@ -87,6 +87,9 @@ public void withdrawMember(Long memberId) { memberExerciseRepository.deleteAllByMember(member); memberPartyRepository.deleteAllByMember(member); + // 카카오 연결 끊기 + kakaoOauthService.unlinkAccess(member); + // 활성화 여부 해제, 리프레시 토큰 삭제 member.withdraw(); } diff --git a/src/main/java/umc/cockple/demo/global/oauth2/domain/KakaoClient.java b/src/main/java/umc/cockple/demo/global/oauth2/domain/KakaoClient.java index 3b9f9674..1d31ee38 100644 --- a/src/main/java/umc/cockple/demo/global/oauth2/domain/KakaoClient.java +++ b/src/main/java/umc/cockple/demo/global/oauth2/domain/KakaoClient.java @@ -34,6 +34,12 @@ public class KakaoClient { @Value("${kakao.user-info-uri}") private String userInfoUri; + @Value("${kakao.unlink-uri}") + private String unlinkUri; + + @Value("$kakao.admin-key") + private String adminKey; + // 인가코드로 AccessToken 요청하기 public String getAccessToken(String code) { @@ -74,5 +80,19 @@ public KakaoClientInfo getClientInfo(String accessToken) { return new KakaoClientInfo(kakaoId, nickname); } + // 회원 탈퇴를 위해 카카오와 연결 끊기 + public void unlinkByAdmin(Long socialId) { + webClient.post() + .uri(unlinkUri) + .header(HttpHeaders.AUTHORIZATION, "KakaoAK " + adminKey) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData("target_id_type", "user_id") + .with("target_id", String.valueOf(socialId))) + .retrieve() + .toBodilessEntity() + .block() + ; + log.info("[KAKAO] unlink OK. kakaoUserId={}", socialId); + } } diff --git a/src/main/java/umc/cockple/demo/global/oauth2/domain/info/KakaoClientInfo.java b/src/main/java/umc/cockple/demo/global/oauth2/domain/info/KakaoClientInfo.java index bed5d299..34205079 100644 --- a/src/main/java/umc/cockple/demo/global/oauth2/domain/info/KakaoClientInfo.java +++ b/src/main/java/umc/cockple/demo/global/oauth2/domain/info/KakaoClientInfo.java @@ -1,7 +1,7 @@ package umc.cockple.demo.global.oauth2.domain.info; public record KakaoClientInfo( - Long kakaoId, + Long kakaoId, // 소셜아이디 String nickname ) { diff --git a/src/main/java/umc/cockple/demo/global/oauth2/service/KakaoOauthService.java b/src/main/java/umc/cockple/demo/global/oauth2/service/KakaoOauthService.java index 99492a9b..a62898c6 100644 --- a/src/main/java/umc/cockple/demo/global/oauth2/service/KakaoOauthService.java +++ b/src/main/java/umc/cockple/demo/global/oauth2/service/KakaoOauthService.java @@ -65,6 +65,17 @@ public KakaoLoginResponseDTO signup(String code) { return new KakaoLoginResponseDTO(accessToken, refreshToken, member.getId(), member.getNickname(), newMember); } + public void unlinkAccess(Member member) { + if (member.getSocialId() != null) { + try { + kakaoClient.unlinkByAdmin(member.getSocialId()); + } catch (Exception e) { + throw new MemberException(MemberErrorCode.OAUTH_UNLINK_FAIL); + } + } + + } + public KakaoLoginResponseDTO createDevToken() { // 특정 member 가져오기 Member member = memberRepository.findById(1L) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8c27e800..f08f6d9a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -73,6 +73,8 @@ kakao: token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v2/user/me redirect-uri: ${KAKAO_REDIRECT_URI} + admin-key: ${KAKAO_ADMIN_KEY} + unlink-uri: https://kapi.kakao.com/v1/user/unlink jwt: secret: ${JWT_SECRET_KEY}