Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Feat: 소셜 로그인 구현(카카오, 네이버)(#36) #47

Merged
merged 1 commit into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/java/com/umc/TheGoods/config/MailConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,11 +16,11 @@ public class MailConfig {
public boolean sendMail(String ToEmail, String code) {
try {
MimeMessage message = javaMailSender.createMimeMessage();
message.setFrom(new InternetAddress("[email protected]", "TheGoods", "UTF-8"));
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

helper.setSubject("TheGoods 인증번호 "); // 제목
helper.setTo(ToEmail); // 받는사람
helper.setFrom("[email protected]");

String verificationCode = code;
String emailBody = String.format("TheGoods 인증번호는 %s입니다.", verificationCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);

Optional<Member> findByPhone(String phone);

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,9 @@ public interface MemberCommandService {

Boolean confirmEmailAuth(MemberRequestDTO.EmailAuthConfirmDTO request);

String kakaoAuth(String code);

String naverAuth(String code, String state);


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 배열)")
Expand Down Expand Up @@ -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));
}


}


25 changes: 25 additions & 0 deletions src/main/java/com/umc/TheGoods/web/dto/member/KakaoProfile.java
Original file line number Diff line number Diff line change
@@ -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;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Loading
Loading