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; +}