diff --git a/src/main/java/com/gongjakso/server/domain/member/enumerate/LoginType.java b/src/main/java/com/gongjakso/server/domain/member/enumerate/LoginType.java index 7cf730d..715e22e 100644 --- a/src/main/java/com/gongjakso/server/domain/member/enumerate/LoginType.java +++ b/src/main/java/com/gongjakso/server/domain/member/enumerate/LoginType.java @@ -1,5 +1,5 @@ package com.gongjakso.server.domain.member.enumerate; public enum LoginType { - GENERAL, KAKAO , GOOGLE + GENERAL, KAKAO , GOOGLE, NAVER } diff --git a/src/main/java/com/gongjakso/server/domain/member/service/AuthService.java b/src/main/java/com/gongjakso/server/domain/member/service/AuthService.java index 2491b9d..87e08b3 100644 --- a/src/main/java/com/gongjakso/server/domain/member/service/AuthService.java +++ b/src/main/java/com/gongjakso/server/domain/member/service/AuthService.java @@ -14,6 +14,9 @@ import com.gongjakso.server.global.security.kakao.KakaoClient; import com.gongjakso.server.global.security.kakao.dto.KakaoProfile; import com.gongjakso.server.global.security.kakao.dto.KakaoToken; +import com.gongjakso.server.global.security.naver.NaverClient; +import com.gongjakso.server.global.security.naver.dto.NaverProfile; +import com.gongjakso.server.global.security.naver.dto.NaverToken; import com.gongjakso.server.global.util.redis.RedisClient; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -29,6 +32,7 @@ public class AuthService { private final RedisClient redisClient; private final TokenProvider tokenProvider; private final MemberRepository memberRepository; + private final NaverClient naverClient; @Transactional public LoginRes signIn(String code, String redirectUri, String type) { @@ -39,7 +43,7 @@ public LoginRes signIn(String code, String redirectUri, String type) { Member member = switch (loginType) { case KAKAO -> kakaoMember(code, redirectUri); case GOOGLE -> googleMember(code, redirectUri); - + case NAVER -> naverMember(code, redirectUri); default -> null; }; @@ -103,6 +107,30 @@ private Member googleMember(String code, String redirectUri) { return member; } + private Member naverMember(String code, String redirectUri){ + // 네이버로 액세스 토큰 요청하기 + NaverToken naverAccessToken = naverClient.getNaverAccessToken(code, redirectUri); + + // 네이버에 있는 사용자 정보 반환 + NaverProfile naverProfile = naverClient.getMemberInfo(naverAccessToken); + + // 반환된 정보의 이메일 기반으로 사용자 테이블에서 계정 정보 조회 진행 + // 이메일 존재 시 로그인 , 존재하지 않을 경우 회원가입 진행 + Member member = memberRepository.findMemberByEmailAndDeletedAtIsNull(naverProfile.response().email()).orElse(null); + + if(member == null) { + Member newMember = Member.builder() + .email(naverProfile.response().email()) + .name(naverProfile.response().name()) + .memberType("GENERAL") + .loginType("NAVER") + .build(); + + return memberRepository.save(newMember); + } + return member; + } + public void signOut(String token, Member member) { // Validation String accessToken = token.substring(7); diff --git a/src/main/java/com/gongjakso/server/global/security/naver/NaverClient.java b/src/main/java/com/gongjakso/server/global/security/naver/NaverClient.java new file mode 100644 index 0000000..9aed2e6 --- /dev/null +++ b/src/main/java/com/gongjakso/server/global/security/naver/NaverClient.java @@ -0,0 +1,101 @@ +package com.gongjakso.server.global.security.naver; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gongjakso.server.global.security.naver.dto.NaverProfile; +import com.gongjakso.server.global.security.naver.dto.NaverToken; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +@Slf4j +@Component +@RequiredArgsConstructor +public class NaverClient { + + @Value("${spring.security.oauth2.client.provider.naver.token-uri}") + private String naverTokenUri; + + @Value("${spring.security.oauth2.client.registration.naver.client-id}") + private String naverClientId; + + @Value("${spring.security.oauth2.client.registration.naver.authorization-grant-type}") + private String naverGrantType; + + @Value("${spring.security.oauth2.client.registration.naver.client-secret}") + private String naverClientSecret; + + @Value("${spring.security.oauth2.client.provider.naver.user-info-uri}") + private String naverUserInfoUri; + + @Value("${spring.security.oauth2.client.provider.naver.authorization-uri}") + private String naverAuthorizationUri; + + /** + * 네이버 서버에 인가코드 기반으로 사용자의 토큰 정보를 조회하는 메소드 + * @param code - 네이버에서 발급해준 인가 코드 + * @return - 네이버에서 반환한 응답 토큰 객체 + */ + public NaverToken getNaverAccessToken(String code, String redirectUri) { + // 요청 보낼 객체 기본 생성 + WebClient webClient = WebClient.create(naverTokenUri); + + //요청 본문 + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", naverGrantType); + params.add("client_id", naverClientId); + params.add("redirect_uri", redirectUri); + params.add("code", code); + params.add("client_secret", naverClientSecret); + + // 요청 보내기 및 응답 수신 + String response = webClient.post() + .uri(naverTokenUri) + .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(); + NaverToken naverToken; + try { + naverToken = objectMapper.readValue(response, NaverToken.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return naverToken; + } + + public NaverProfile getMemberInfo(NaverToken naverToken) { + // 요청 기본 객체 생성 + WebClient webClient = WebClient.create(naverUserInfoUri); + + // 요청 보내서 응답 받기 + String response = webClient.post() + .uri(naverUserInfoUri) + .header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") + .header("Authorization", "Bearer " + naverToken.access_token()) + .retrieve() + .bodyToMono(String.class) + .block(); + + // 수신된 응답 Mapping + ObjectMapper objectMapper = new ObjectMapper(); + NaverProfile naverProfile; + try { + naverProfile = objectMapper.readValue(response, NaverProfile.class); + + } catch (Exception e) { + throw new RuntimeException(e); + } + + return naverProfile; + } +} diff --git a/src/main/java/com/gongjakso/server/global/security/naver/NaverUserService.java b/src/main/java/com/gongjakso/server/global/security/naver/NaverUserService.java new file mode 100644 index 0000000..b3a36fc --- /dev/null +++ b/src/main/java/com/gongjakso/server/global/security/naver/NaverUserService.java @@ -0,0 +1,18 @@ +package com.gongjakso.server.global.security.naver; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class NaverUserService extends DefaultOAuth2UserService { + + @Override + public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException { + return null; + } +} diff --git a/src/main/java/com/gongjakso/server/global/security/naver/dto/NaverProfile.java b/src/main/java/com/gongjakso/server/global/security/naver/dto/NaverProfile.java new file mode 100644 index 0000000..6836e1f --- /dev/null +++ b/src/main/java/com/gongjakso/server/global/security/naver/dto/NaverProfile.java @@ -0,0 +1,25 @@ +package com.gongjakso.server.global.security.naver.dto; + +import lombok.Builder; + +@Builder +public record NaverProfile( + String resultcode, + String message, + NaverProfileResponse response +) { + public record NaverProfileResponse( + String id, + String name, + String nickname, + String profile_image, + String email, + String age, + String birthday, + String gender, + String mobile, + String mobile_e164, + String birthyear + ) { + } +} diff --git a/src/main/java/com/gongjakso/server/global/security/naver/dto/NaverToken.java b/src/main/java/com/gongjakso/server/global/security/naver/dto/NaverToken.java new file mode 100644 index 0000000..cd79d55 --- /dev/null +++ b/src/main/java/com/gongjakso/server/global/security/naver/dto/NaverToken.java @@ -0,0 +1,16 @@ +package com.gongjakso.server.global.security.naver.dto; + +import jakarta.validation.constraints.Null; +import lombok.Builder; + +@Builder +public record NaverToken( + String access_token, + @Null + String refresh_token, + int expires_in, + String token_type, + String error, + String error_description +) { +} \ No newline at end of file