From 8c4cc1e197f86b4b7f737935aadee611bcfbce7d Mon Sep 17 00:00:00 2001
From: joon <khjoon372@naver.com>
Date: Tue, 6 Feb 2024 00:59:42 +0900
Subject: [PATCH] =?UTF-8?q?:sparkles:=20Feat:=20=EC=86=8C=EC=85=9C=20?=
 =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84(=EC=B9=B4?=
 =?UTF-8?q?=EC=B9=B4=EC=98=A4,=20=EB=84=A4=EC=9D=B4=EB=B2=84)(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/umc/TheGoods/config/MailConfig.java   |   3 +-
 .../converter/member/MemberConverter.java     |  17 ++
 .../repository/member/MemberRepository.java   |   1 +
 .../MemberService/MemberCommandService.java   |   4 +
 .../MemberCommandServiceImpl.java             | 185 ++++++++++++++++++
 .../web/controller/MemberController.java      |  41 +++-
 .../TheGoods/web/dto/member/KakaoProfile.java |  25 +++
 .../web/dto/member/MemberResponseDTO.java     |  18 ++
 .../TheGoods/web/dto/member/NaverProfile.java |  22 +++
 .../TheGoods/web/dto/member/OAuthToken.java   |  14 ++
 10 files changed, 322 insertions(+), 8 deletions(-)
 create mode 100644 src/main/java/com/umc/TheGoods/web/dto/member/KakaoProfile.java
 create mode 100644 src/main/java/com/umc/TheGoods/web/dto/member/NaverProfile.java
 create mode 100644 src/main/java/com/umc/TheGoods/web/dto/member/OAuthToken.java

diff --git a/src/main/java/com/umc/TheGoods/config/MailConfig.java b/src/main/java/com/umc/TheGoods/config/MailConfig.java
index 765dd4c4..7aa9956d 100644
--- a/src/main/java/com/umc/TheGoods/config/MailConfig.java
+++ b/src/main/java/com/umc/TheGoods/config/MailConfig.java
@@ -5,6 +5,7 @@
 import org.springframework.mail.javamail.MimeMessageHelper;
 import org.springframework.stereotype.Component;
 
+import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
 
 @Component
@@ -15,11 +16,11 @@ public class MailConfig {
     public boolean sendMail(String ToEmail, String code) {
         try {
             MimeMessage message = javaMailSender.createMimeMessage();
+            message.setFrom(new InternetAddress("khjoon8010@gmail.com", "TheGoods", "UTF-8"));
             MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
 
             helper.setSubject("TheGoods 인증번호 "); // 제목
             helper.setTo(ToEmail); // 받는사람
-            helper.setFrom("thegoodssys@gmail.com");
 
             String verificationCode = code;
             String emailBody = String.format("TheGoods 인증번호는 %s입니다.", verificationCode);
diff --git a/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java b/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java
index dce86aa7..62cca325 100644
--- a/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java
+++ b/src/main/java/com/umc/TheGoods/converter/member/MemberConverter.java
@@ -133,8 +133,25 @@ public static Auth toEmailAuth(String email, String code, Boolean expired) {
     }
 
     public static MemberResponseDTO.EmailAuthConfirmResultDTO toEmailAuthConfirmResultDTO(Boolean checkEmail) {
+
+
         return MemberResponseDTO.EmailAuthConfirmResultDTO.builder()
                 .checkEmail(checkEmail)
                 .build();
     }
+
+    public static MemberResponseDTO.SocialLoginResultDTO toSocialLoginResultDTO(String result) {
+
+        return MemberResponseDTO.SocialLoginResultDTO.builder()
+                .result(result)
+                .build();
+    }
+
+    public static MemberResponseDTO.SocialJoinResultDTO toSocialJoinResultDTO(String phone, String email) {
+
+        return MemberResponseDTO.SocialJoinResultDTO.builder()
+                .phone(phone)
+                .email(email)
+                .build();
+    }
 }
diff --git a/src/main/java/com/umc/TheGoods/repository/member/MemberRepository.java b/src/main/java/com/umc/TheGoods/repository/member/MemberRepository.java
index 9813c810..8230c04b 100644
--- a/src/main/java/com/umc/TheGoods/repository/member/MemberRepository.java
+++ b/src/main/java/com/umc/TheGoods/repository/member/MemberRepository.java
@@ -11,4 +11,5 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
     Optional<Member> findByEmail(String email);
 
     Optional<Member> findByPhone(String phone);
+
 }
diff --git a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java
index 1c144f01..9b038e2c 100644
--- a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java
+++ b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandService.java
@@ -29,5 +29,9 @@ public interface MemberCommandService {
 
     Boolean confirmEmailAuth(MemberRequestDTO.EmailAuthConfirmDTO request);
 
+    String kakaoAuth(String code);
+
+    String naverAuth(String code, String state);
+
 
 }
diff --git a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java
index 6637764a..c0b593d4 100644
--- a/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java
+++ b/src/main/java/com/umc/TheGoods/service/MemberService/MemberCommandServiceImpl.java
@@ -16,7 +16,10 @@
 import com.umc.TheGoods.repository.member.CategoryRepository;
 import com.umc.TheGoods.repository.member.MemberRepository;
 import com.umc.TheGoods.repository.member.TermRepository;
+import com.umc.TheGoods.web.dto.member.KakaoProfile;
 import com.umc.TheGoods.web.dto.member.MemberRequestDTO;
+import com.umc.TheGoods.web.dto.member.NaverProfile;
+import com.umc.TheGoods.web.dto.member.OAuthToken;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
@@ -27,6 +30,8 @@
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.RestTemplate;
 
 import java.time.LocalDateTime;
@@ -298,6 +303,186 @@ public Boolean confirmEmailAuth(MemberRequestDTO.EmailAuthConfirmDTO request) {
         return checkEmail;
     }
 
+    @Override
+    @Transactional
+    public String kakaoAuth(String code) {
+
+        String jwt;
+
+        //Post방식으로 key=value 데이터를 요청
+        RestTemplate rt = new RestTemplate();
+
+        //HttpHeader 객체 생성
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
+
+        //HttpBody 객체 생성
+        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+        params.add("grant_type", "authorization_code");
+        params.add("client_id", "49ff7dc7f5309c49f75ac2a087ffe91e");
+        params.add("redirect_uri", "http://localhost:8080/api/members/kakao/callback");
+        params.add("code", code);
+        //params.add("client_secret","");
+
+        //HttpHeader와 HttpBody를 하나의 객체로 담기
+        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
+                new HttpEntity<>(params, headers);
+
+        //Http 요청해서 응답 받음
+        ResponseEntity<String> response = rt.exchange(
+                "https://kauth.kakao.com/oauth/token",
+                HttpMethod.POST,
+                kakaoTokenRequest,
+                String.class
+        );
+
+        ObjectMapper objectMapper = new ObjectMapper();
+        OAuthToken oAuthToken = null;
+
+        try {
+            oAuthToken = objectMapper.readValue(response.getBody(), OAuthToken.class);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+        log.info("카카오 토큰:" + oAuthToken.getAccess_token());
+
+
+        ///////////////////////////
+        //토큰 이용해서 정보 가져오기
+
+        RestTemplate rt2 = new RestTemplate();
+
+        HttpHeaders headers2 = new HttpHeaders();
+        headers2.add("Authorization", "Bearer " + oAuthToken.getAccess_token());
+        headers2.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
+
+
+        //HttpHeader와 HttpBody를 하나의 객체로 담기
+        HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest =
+                new HttpEntity<>(headers2);
+
+        ResponseEntity<String> response2 = rt2.exchange(
+                "https://kapi.kakao.com/v2/user/me",
+                HttpMethod.POST,
+                kakaoProfileRequest,
+                String.class
+        );
+
+
+        ObjectMapper objectMapper2 = new ObjectMapper();
+        KakaoProfile kakaoProfile = null;
+
+        try {
+            kakaoProfile = objectMapper2.readValue(response2.getBody(), KakaoProfile.class);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+        String phone = kakaoProfile.kakao_account.phone_number.replaceAll("[^0-9]", "");
+        if (phone.startsWith("82")) {
+            phone = "0" + phone.substring(2);
+        }
+
+
+        Optional<Member> member = memberRepository.findByPhone(phone);
+
+        if (member.isPresent()) {
+            List<String> roles = new ArrayList<>();
+            roles.add("ROLE_USER");
+
+            return createJwt(member.get().getId(), member.get().getNickname(), expiredMs, key, roles);
+
+        }
+
+        return phone + kakaoProfile.getKakao_account().email;
+    }
+
+    @Override
+    @Transactional
+    public String naverAuth(String code, String state) {
+        RestTemplate rt = new RestTemplate();
+
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
+
+        //HttpBody 객체 생성
+        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+        params.add("grant_type", "authorization_code");
+        params.add("client_id", "t6q4Bn70dY7Kli7hS58P");
+        params.add("redirect_uri", "http://localhost:8080/api/members/naver/callback");
+        params.add("client_secret", "1uPpEHHTBF");
+        params.add("code", code);
+        params.add("state", state);
+
+
+        //HttpHeader와 HttpBody를 하나의 객체로 담기
+        HttpEntity<MultiValueMap<String, String>> naverTokenRequest =
+                new HttpEntity<>(params, headers);
+
+        //Http 요청해서 응답 받음
+        ResponseEntity<String> response = rt.exchange(
+                "https://nid.naver.com/oauth2.0/token",
+                HttpMethod.POST,
+                naverTokenRequest,
+                String.class
+        );
+
+        ObjectMapper objectMapper = new ObjectMapper();
+        OAuthToken oAuthToken = null;
+
+        try {
+            oAuthToken = objectMapper.readValue(response.getBody(), OAuthToken.class);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+        log.info("네이버 토큰:" + oAuthToken.getAccess_token());
+
+
+        RestTemplate rt2 = new RestTemplate();
+
+        HttpHeaders headers2 = new HttpHeaders();
+        headers2.add("Authorization", "Bearer " + oAuthToken.getAccess_token());
+        headers2.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
+
+
+        //HttpHeader와 HttpBody를 하나의 객체로 담기
+        HttpEntity<MultiValueMap<String, String>> naverProfileRequest =
+                new HttpEntity<>(headers2);
+
+        ResponseEntity<String> response2 = rt2.exchange(
+                "https://openapi.naver.com/v1/nid/me",
+                HttpMethod.POST,
+                naverProfileRequest,
+                String.class
+        );
+
+        log.info(response2.toString());
+        ObjectMapper objectMapper2 = new ObjectMapper();
+        NaverProfile naverProfile = null;
+
+        try {
+            naverProfile = objectMapper2.readValue(response2.getBody(), NaverProfile.class);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+        String phone = naverProfile.response.mobile.replaceAll("[^0-9]", "");
+
+        Optional<Member> member = memberRepository.findByPhone(phone);
+
+        if (member.isPresent()) {
+            List<String> roles = new ArrayList<>();
+            roles.add("ROLE_USER");
+
+            return createJwt(member.get().getId(), member.get().getNickname(), expiredMs, key, roles);
+
+        }
+
+        return phone + naverProfile.getResponse().email;
+    }
+
     /**
      * 카테고리 validator
      */
diff --git a/src/main/java/com/umc/TheGoods/web/controller/MemberController.java b/src/main/java/com/umc/TheGoods/web/controller/MemberController.java
index e943177e..e211e351 100644
--- a/src/main/java/com/umc/TheGoods/web/controller/MemberController.java
+++ b/src/main/java/com/umc/TheGoods/web/controller/MemberController.java
@@ -16,10 +16,7 @@
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.core.Authentication;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 
@@ -33,9 +30,6 @@ public class MemberController {
 
     private final MemberCommandService memberCommandService;
 
-    public static final String ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
-    public static final String AUTH_TOKEN = "your_auth_token";
-
 
     @PostMapping("/join")
     @Operation(summary = "회원가입 API", description = "request 파라미터 : 닉네임, 비밀번호(String), 이메일, 생일(yyyymmdd), 성별(MALE, FEMALE, NO_SELECET), 폰번호(010xxxxxxxx),이용약관(Boolean 배열), 카테고리(Long 배열)")
@@ -129,6 +123,39 @@ public ApiResponse<MemberResponseDTO.EmailAuthConfirmResultDTO> emailAuth(@Reque
         return ApiResponse.onSuccess(MemberConverter.toEmailAuthConfirmResultDTO(checkEmail));
     }
 
+
+    @GetMapping("/kakao/callback")
+    public ApiResponse<?> kakaoCallback(@RequestParam String code) {
+
+        String result = memberCommandService.kakaoAuth(code);
+
+        //반환한 카카오 프로필에서 기존 회원이면 jwt 토큰 반환 아니면 회원가입 진행
+        //토큰 반환은 쉽지만 회원가입 로직으로 보내야하면 false 반환해서 회원가입 진행하도록하기
+
+        if (result.startsWith("010")) {
+            String phone = result.substring(0, 11);
+            String email = result.substring(11);
+            return ApiResponse.onFailure("로그인 실패", "회원가입 필요", MemberConverter.toSocialJoinResultDTO(phone, email));
+        }
+
+        return ApiResponse.onSuccess(MemberConverter.toSocialLoginResultDTO(result));
+    }
+
+    @GetMapping("/naver/callback")
+    public ApiResponse<?> naverCallback(@RequestParam String code, String state) {
+
+        String result = memberCommandService.naverAuth(code, state);
+
+        if (result.startsWith("010")) {
+            String phone = result.substring(0, 11);
+            String email = result.substring(11);
+            return ApiResponse.onFailure("로그인 실패", "회원가입 필요", MemberConverter.toSocialJoinResultDTO(phone, email));
+        }
+
+        return ApiResponse.onSuccess(MemberConverter.toSocialLoginResultDTO(result));
+    }
+
+
 }
 
 
diff --git a/src/main/java/com/umc/TheGoods/web/dto/member/KakaoProfile.java b/src/main/java/com/umc/TheGoods/web/dto/member/KakaoProfile.java
new file mode 100644
index 00000000..63551fda
--- /dev/null
+++ b/src/main/java/com/umc/TheGoods/web/dto/member/KakaoProfile.java
@@ -0,0 +1,25 @@
+package com.umc.TheGoods.web.dto.member;
+
+import lombok.Data;
+
+@Data
+public class KakaoProfile {
+
+    public Long id;
+    public String connected_at;
+    public KakaoAccount kakao_account;
+
+    @Data
+    public class KakaoAccount {
+
+        public Boolean has_email;
+        public Boolean email_needs_agreement;
+        public Boolean is_email_valid;
+        public Boolean is_email_verified;
+        public String email;
+        public Boolean has_phone_number;
+        public Boolean phone_number_needs_agreement;
+        public String phone_number;
+
+    }
+}
diff --git a/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java b/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java
index 59c607c0..3e302dd1 100644
--- a/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java
+++ b/src/main/java/com/umc/TheGoods/web/dto/member/MemberResponseDTO.java
@@ -90,4 +90,22 @@ public static class EmailAuthConfirmResultDTO {
         Boolean checkEmail;
     }
 
+
+    @Builder
+    @Getter
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SocialLoginResultDTO {
+        String result;
+    }
+
+    @Builder
+    @Getter
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SocialJoinResultDTO {
+        String phone;
+        String email;
+    }
+
 }
diff --git a/src/main/java/com/umc/TheGoods/web/dto/member/NaverProfile.java b/src/main/java/com/umc/TheGoods/web/dto/member/NaverProfile.java
new file mode 100644
index 00000000..9eba546b
--- /dev/null
+++ b/src/main/java/com/umc/TheGoods/web/dto/member/NaverProfile.java
@@ -0,0 +1,22 @@
+package com.umc.TheGoods.web.dto.member;
+
+import lombok.Data;
+
+@Data
+public class NaverProfile {
+
+    public String resultcode;
+    public String message;
+    public Response response;
+
+    @Data
+    public class Response {
+
+        public String id;
+        public String email;
+        public String mobile;
+        public String mobile_e164;
+
+    }
+
+}
diff --git a/src/main/java/com/umc/TheGoods/web/dto/member/OAuthToken.java b/src/main/java/com/umc/TheGoods/web/dto/member/OAuthToken.java
new file mode 100644
index 00000000..408aa577
--- /dev/null
+++ b/src/main/java/com/umc/TheGoods/web/dto/member/OAuthToken.java
@@ -0,0 +1,14 @@
+package com.umc.TheGoods.web.dto.member;
+
+import lombok.Data;
+
+@Data
+public class OAuthToken {
+
+    private String access_token;
+    private String token_type;
+    private String refresh_token;
+    private int expires_in;
+    private String scope;
+    private int refresh_token_expires_in;
+}