From e9cd36d406891da4133d5e355fb0d94fef7fd4eb Mon Sep 17 00:00:00 2001 From: sycuuui <102959791+sycuuui@users.noreply.github.com> Date: Sun, 22 Sep 2024 23:53:04 +0900 Subject: [PATCH] =?UTF-8?q?#14=20feat=20:=20kakao=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=81=8A=EA=B8=B0=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/service/AuthService.java | 13 +++- .../Together/global/exception/ErrorCode.java | 3 +- .../global/security/kakao/KakaoClient.java | 66 ++++++++++++++----- .../global/security/kakao/dto/KakaoToken.java | 14 ++++ 4 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 src/main/java/Journey/Together/global/security/kakao/dto/KakaoToken.java diff --git a/src/main/java/Journey/Together/domain/member/service/AuthService.java b/src/main/java/Journey/Together/domain/member/service/AuthService.java index 87d235d..2614e0c 100644 --- a/src/main/java/Journey/Together/domain/member/service/AuthService.java +++ b/src/main/java/Journey/Together/domain/member/service/AuthService.java @@ -16,6 +16,7 @@ import Journey.Together.global.security.kakao.dto.KakaoProfile; import Journey.Together.global.security.jwt.TokenProvider; import Journey.Together.global.security.jwt.dto.TokenDto; +import Journey.Together.global.security.kakao.dto.KakaoToken; import Journey.Together.global.security.naver.dto.NaverDeleteResponse; import Journey.Together.global.security.naver.dto.NaverProperties; import Journey.Together.global.security.naver.dto.NaverTokenResponse; @@ -89,7 +90,7 @@ public LoginRes signIn(String token, String type, LoginReq loginReq) throws IOEx interestRepository.save(interest); } tokenDto = tokenProvider.createToken(member); - member.setRefreshToken(tokenDto.refreshToken()); + member.setRefreshToken(loginReq.refreshToken()); // Response return LoginRes.of(member, tokenDto); @@ -156,6 +157,16 @@ public void withdrawal(Member member) { if(naverDeleteResponse.getError() != null){ throw new ApplicationException(ErrorCode.NAVER_DELETE_ERROR); } + }else if(member.getLoginType().equals(LoginType.KAKAO)) { + //accessToken 요청 + KakaoToken kakaoToken = kakaoClient.getKakaoAccessToken(member.getRefreshToken()); + //연결 삭제 + Long id = kakaoClient.unlinkUser(kakaoToken.access_token()); + if(id==null){ + throw new ApplicationException(ErrorCode.NAVER_REFRESH_ERROR); + }else if(!id.equals(member.getMemberId())){ + throw new ApplicationException(ErrorCode.KAKAO_DELETE_ERROR); + } } memberRepository.delete(member); diff --git a/src/main/java/Journey/Together/global/exception/ErrorCode.java b/src/main/java/Journey/Together/global/exception/ErrorCode.java index e211fab..5f043ed 100644 --- a/src/main/java/Journey/Together/global/exception/ErrorCode.java +++ b/src/main/java/Journey/Together/global/exception/ErrorCode.java @@ -29,7 +29,8 @@ public enum ErrorCode { LOGOUT_TOKEN_EXCEPTION(HttpStatus.UNAUTHORIZED, 3003, "로그아웃된 토큰입니다"), WRONG_TOKEN(HttpStatus.UNAUTHORIZED, 3004, "유효하지 않은 토큰입니다."), WRONG_ACCESS_EXCEPTION(HttpStatus.BAD_REQUEST, 3005, "관리자만 접근 가능합니다."), - + KAKAO_REFRESH_TOKEN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 3006, "카카오 토큰에 오류가 있습니다"), + KAKAO_DELETE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 3007, "카카오 연결 끊기에 실패하였습니다"), //4000: Apply Error NOT_APPLY_EXCEPTION(HttpStatus.BAD_REQUEST,4000,"지원 기간 지났습니다"), diff --git a/src/main/java/Journey/Together/global/security/kakao/KakaoClient.java b/src/main/java/Journey/Together/global/security/kakao/KakaoClient.java index 288e4e1..9a6abd5 100644 --- a/src/main/java/Journey/Together/global/security/kakao/KakaoClient.java +++ b/src/main/java/Journey/Together/global/security/kakao/KakaoClient.java @@ -1,6 +1,8 @@ package Journey.Together.global.security.kakao; import Journey.Together.global.security.kakao.dto.KakaoProfile; +import Journey.Together.global.security.kakao.dto.KakaoToken; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,9 +23,6 @@ public class KakaoClient { @Value("${spring.security.oauth2.client.registration.kakao.client-secret}") private String kakaoClientSecret; - @Value("${spring.security.oauth2.client.registration.kakao.authorization-grant-type}") - private String kakwaoGrantType; - @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") private String kakaoRedirectUri; @@ -39,6 +38,44 @@ public class KakaoClient { @Value("${spring.security.oauth2.client.withdrawal.unlink-url}") private String unlinkUri; + + /** + * 카카오 서버에 인가코드 기반으로 사용자의 토큰 정보를 조회하는 메소드 + * @param refresh_token - 카카오에서 발급해준 refreshToken 코드 + * @return - 카카오에서 반환한 응답 토큰 객체 + */ + public KakaoToken getKakaoAccessToken(String refresh_token) { + // 요청 보낼 객체 기본 생성 + WebClient webClient = WebClient.create(kakaoTokenUri); + + //요청 본문 + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "refresh_token"); + params.add("client_id", kakaoClientId); + params.add("refresh_token", refresh_token); + params.add("client_secret", kakaoClientSecret); + + // 요청 보내기 및 응답 수신 + String response = webClient.post() + .uri(kakaoTokenUri) + .header("Content-type", "application/x-www-form-urlencoded") + .body(BodyInserters.fromFormData(params)) + .retrieve() // 데이터 받는 방식, 스프링에서는 exchange는 메모리 누수 가능성 때문에 retrieve 권장 + .bodyToMono(String.class) // (Mono는 단일 데이터, Flux는 복수 데이터) + .block();// 비동기 방식의 데이터 수신 + + // 수신된 응답 Mapping + ObjectMapper objectMapper = new ObjectMapper(); + KakaoToken kakaoToken; + try { + kakaoToken = objectMapper.readValue(response, KakaoToken.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return kakaoToken; + } + public KakaoProfile getMemberInfo(String accesToken) { // 요청 기본 객체 생성 WebClient webClient = WebClient.create(kakaoUserInfoUri); @@ -64,28 +101,27 @@ public KakaoProfile getMemberInfo(String accesToken) { } //카카오와 연결 끊기 - public String unlinkUser(Long userId){ + public Long unlinkUser(String accessToken){ // 요청 기본 객체 생성 WebClient webClient = WebClient.create(unlinkUri); // 요청 보내서 응답 받기 String response = webClient.post() .uri(unlinkUri) .header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") - .header("Authorization", "KakaoAK " + adminKey) + .header("Authorization", "Bearer " + accessToken) .retrieve() .bodyToMono(String.class) .block(); // 수신된 응답 Mapping ObjectMapper objectMapper = new ObjectMapper(); - - return response; + try { + JsonNode jsonNode = objectMapper.readTree(response); + if (jsonNode.has("id")) { + return jsonNode.get("id").asLong(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return null; } -// public String unlinkUser(Long userId) { -// -// URI uri = URI.create(KAKAO_UNLINK_URL + "?target_id_type=user_id&target_id=" + userId); -// -// RequestEntity request = new RequestEntity<>(headers, HttpMethod.POST, uri); -// ResponseEntity response = restTemplate.exchange(request, String.class); -// -// } } diff --git a/src/main/java/Journey/Together/global/security/kakao/dto/KakaoToken.java b/src/main/java/Journey/Together/global/security/kakao/dto/KakaoToken.java new file mode 100644 index 0000000..eaa1eb2 --- /dev/null +++ b/src/main/java/Journey/Together/global/security/kakao/dto/KakaoToken.java @@ -0,0 +1,14 @@ +package Journey.Together.global.security.kakao.dto; + +import lombok.Builder; + +@Builder +public record KakaoToken( + String access_token, + String refresh_token, + String token_type, + Integer expires_in, + Integer refresh_token_expires_in, + String scope +) { +}