From 28d8465e2159dd826f184b094df525a637042b2c Mon Sep 17 00:00:00 2001 From: DDonghyeo Date: Thu, 23 May 2024 15:56:27 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8feat=20:=20OAuth2=20Kakao=20Login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/OAuthController.java | 36 +++ .../userservice/dto/response/KakaoResDto.java | 220 ++++++++++++++++++ .../userservice/service/KakaoService.java | 83 +++++++ user-service/src/main/resources/bootstrap.yml | 2 +- 4 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 user-service/src/main/java/com/waither/userservice/controller/OAuthController.java create mode 100644 user-service/src/main/java/com/waither/userservice/dto/response/KakaoResDto.java create mode 100644 user-service/src/main/java/com/waither/userservice/service/KakaoService.java diff --git a/user-service/src/main/java/com/waither/userservice/controller/OAuthController.java b/user-service/src/main/java/com/waither/userservice/controller/OAuthController.java new file mode 100644 index 00000000..6483c13a --- /dev/null +++ b/user-service/src/main/java/com/waither/userservice/controller/OAuthController.java @@ -0,0 +1,36 @@ +package com.waither.userservice.controller; + +import com.waither.userservice.dto.response.KakaoResDto; +import com.waither.userservice.global.response.ApiResponse; +import com.waither.userservice.service.AccountsService; +import com.waither.userservice.service.KakaoService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/oauth") +public class OAuthController { + + private final KakaoService kakaoService; + + private final AccountsService accountsService; + + @GetMapping("/callback") + public ApiResponse callback(@RequestParam("code") String code) { + + String accessTokenFromKakao = kakaoService.getAccessTokenFromKakao(code); + + KakaoResDto.UserInfoResponseDto userInfo = kakaoService.getUserInfo(accessTokenFromKakao); + + //TODO: if 회원가입 안 한 상태 -> 회원가입 진행 + + //TODO: 토큰 발급 + //accountsService + return ApiResponse.onSuccess(null); + } +} diff --git a/user-service/src/main/java/com/waither/userservice/dto/response/KakaoResDto.java b/user-service/src/main/java/com/waither/userservice/dto/response/KakaoResDto.java new file mode 100644 index 00000000..142f6156 --- /dev/null +++ b/user-service/src/main/java/com/waither/userservice/dto/response/KakaoResDto.java @@ -0,0 +1,220 @@ +package com.waither.userservice.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.HashMap; + +public class KakaoResDto { + + @Getter + @NoArgsConstructor //역직렬화를 위한 기본 생성자 + @JsonIgnoreProperties(ignoreUnknown = true) + public static class TokenResponseDto { + + @JsonProperty("token_type") + public String tokenType; + @JsonProperty("access_token") + public String accessToken; + @JsonProperty("id_token") + public String idToken; + @JsonProperty("expires_in") + public Integer expiresIn; + @JsonProperty("refresh_token") + public String refreshToken; + @JsonProperty("refresh_token_expires_in") + public Integer refreshTokenExpiresIn; + @JsonProperty("scope") + public String scope; + } + + @Getter + @NoArgsConstructor //역직렬화를 위한 기본 생성자 + @JsonIgnoreProperties(ignoreUnknown = true) + public static class UserInfoResponseDto { + + /** + * 동의 항목 + * 닉네임, 이메일 필수 동의 + */ + + //회원 번호 + @JsonProperty("id") + public Long id; + + //자동 연결 설정을 비활성화한 경우만 존재. + //true : 연결 상태, false : 연결 대기 상태 + @JsonProperty("has_signed_up") + public Boolean hasSignedUp; + + //서비스에 연결 완료된 시각. UTC + @JsonProperty("connected_at") + public Date connectedAt; + + //카카오싱크 간편가입을 통해 로그인한 시각. UTC + @JsonProperty("synched_at") + public Date synchedAt; + + //사용자 프로퍼티 + @JsonProperty("properties") + public HashMap properties; + + //카카오 계정 정보 + @JsonProperty("kakao_account") + public KakaoAccount kakaoAccount; + + //uuid 등 추가 정보 + @JsonProperty("for_partner") + public Partner partner; + + @Getter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public class KakaoAccount { + + //프로필 정보 제공 동의 여부 + @JsonProperty("profile_needs_agreement") + public Boolean isProfileAgree; + + //닉네임 제공 동의 여부 + @JsonProperty("profile_nickname_needs_agreement") + public Boolean isNickNameAgree; + + //프로필 사진 제공 동의 여부 + @JsonProperty("profile_image_needs_agreement") + public Boolean isProfileImageAgree; + + //사용자 프로필 정보 + @JsonProperty("profile") + public Profile profile; + + //이름 제공 동의 여부 + @JsonProperty("name_needs_agreement") + public Boolean isNameAgree; + + //카카오계정 이름 + @JsonProperty("name") + public String name; + + //이메일 제공 동의 여부 + @JsonProperty("email_needs_agreement") + public Boolean isEmailAgree; + + //이메일이 유효 여부 + // true : 유효한 이메일, false : 이메일이 다른 카카오 계정에 사용돼 만료 + @JsonProperty("is_email_valid") + public Boolean isEmailValid; + + //이메일이 인증 여부 + //true : 인증된 이메일, false : 인증되지 않은 이메일 + @JsonProperty("is_email_verified") + public Boolean isEmailVerified; + + //카카오계정 대표 이메일 + @JsonProperty("email") + public String email; + + //연령대 제공 동의 여부 + @JsonProperty("age_range_needs_agreement") + public Boolean isAgeAgree; + + //연령대 + //참고 https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info + @JsonProperty("age_range") + public String ageRange; + + //출생 연도 제공 동의 여부 + @JsonProperty("birthyear_needs_agreement") + public Boolean isBirthYearAgree; + + //출생 연도 (YYYY 형식) + @JsonProperty("birthyear") + public String birthYear; + + //생일 제공 동의 여부 + @JsonProperty("birthday_needs_agreement") + public Boolean isBirthDayAgree; + + //생일 (MMDD 형식) + @JsonProperty("birthday") + public String birthDay; + + //생일 타입 + // SOLAR(양력) 혹은 LUNAR(음력) + @JsonProperty("birthday_type") + public String birthDayType; + + //성별 제공 동의 여부 + @JsonProperty("gender_needs_agreement") + public Boolean isGenderAgree; + + //성별 + @JsonProperty("gender") + public String gender; + + //전화번호 제공 동의 여부 + @JsonProperty("phone_number_needs_agreement") + public Boolean isPhoneNumberAgree; + + //전화번호 + //국내 번호인 경우 +82 00-0000-0000 형식 + @JsonProperty("phone_number") + public String phoneNumber; + + //CI 동의 여부 + @JsonProperty("ci_needs_agreement") + public Boolean isCIAgree; + + //CI, 연계 정보 + @JsonProperty("ci") + public String ci; + + //CI 발급 시각, UTC + @JsonProperty("ci_authenticated_at") + public Date ciCreatedAt; + + @Getter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public class Profile { + + //닉네임 + @JsonProperty("nickname") + public String nickName; + + //프로필 미리보기 이미지 URL + @JsonProperty("thumbnail_image_url") + public String thumbnailImageUrl; + + //프로필 사진 URL + @JsonProperty("profile_image_url") + public String profileImageUrl; + + //프로필 사진 URL 기본 프로필인지 여부 + //true : 기본 프로필, false : 사용자 등록 + @JsonProperty("is_default_image") + public String isDefaultImage; + + //닉네임이 기본 닉네임인지 여부 + //true : 기본 닉네임, false : 사용자 등록 + @JsonProperty("is_default_nickname") + public Boolean isDefaultNickName; + + } + } + + @Getter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public class Partner { + //고유 ID + @JsonProperty("uuid") + public String uuid; + } + + } + +} diff --git a/user-service/src/main/java/com/waither/userservice/service/KakaoService.java b/user-service/src/main/java/com/waither/userservice/service/KakaoService.java new file mode 100644 index 00000000..b5ff8bb6 --- /dev/null +++ b/user-service/src/main/java/com/waither/userservice/service/KakaoService.java @@ -0,0 +1,83 @@ +package com.waither.userservice.service; + +import com.waither.userservice.dto.response.KakaoResDto; +import io.netty.handler.codec.http.HttpHeaderValues; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@Slf4j +@Service +@RequiredArgsConstructor +public class KakaoService { + + private String client_id; + private final String KATUH_TOKEN_URL; + private final String KAUTH_USER_URL; + + @Autowired + public KakaoService(@Value("${kakao.client_id}") String client_id) { + this.client_id = client_id; + this.KATUH_TOKEN_URL = "https://kauth.kakao.com/oauth/token"; + this.KAUTH_USER_URL = "https://kapi.kakao.com/v2/user/me"; + } + + public String getAccessTokenFromKakao(String code) { + + KakaoResDto.TokenResponseDto kakaoTokenResponseDto = WebClient.create().post() + .uri(uriBuilder -> uriBuilder + .path(KATUH_TOKEN_URL) + .queryParam("grant_type", "authorization_code") + .queryParam("client_id", client_id) + .queryParam("code", code) + .build()) + .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) + .retrieve() + //TODO : Custom Exception + .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter"))) + .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error"))) + .bodyToMono(KakaoResDto.TokenResponseDto.class) + .block(); + + +// log.info(" [Kakao Service] Access Token ------> {}", kakaoTokenResponseDto.getAccessToken()); +// log.info(" [Kakao Service] Refresh Token ------> {}", kakaoTokenResponseDto.getRefreshToken()); + //제공 조건: OpenID Connect가 활성화 된 앱의 토큰 발급 요청인 경우 또는 scope에 openid를 포함한 추가 항목 동의 받기 요청을 거친 토큰 발급 요청인 경우 +// log.info(" [Kakao Service] Id Token ------> {}", kakaoTokenResponseDto.getIdToken()); +// log.info(" [Kakao Service] Scope ------> {}", kakaoTokenResponseDto.getScope()); + + return kakaoTokenResponseDto.getAccessToken(); + } + + + + + public KakaoResDto.UserInfoResponseDto getUserInfo(String accessToken) { + + KakaoResDto.UserInfoResponseDto kakaoUserInfo = WebClient.create() + .get() + .uri(uriBuilder -> uriBuilder + .path(KAUTH_USER_URL) + .build()) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) // access token 인가 + .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) + .retrieve() + //TODO : Custom Exception + .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter"))) + .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error"))) + .bodyToMono(KakaoResDto.UserInfoResponseDto.class) + .block(); + +// log.info("[ Kakao Service ] Auth ID ---> {} ", kakaoUserInfo.getId()); +// log.info("[ Kakao Service ] NickName ---> {} ", kakaoUserInfo.getKakaoAccount().getProfile().getNickName()); +// log.info("[ Kakao Service ] ProfileImageUrl ---> {} ", kakaoUserInfo.getKakaoAccount().getProfile().getProfileImageUrl()); + + return kakaoUserInfo; + } +} diff --git a/user-service/src/main/resources/bootstrap.yml b/user-service/src/main/resources/bootstrap.yml index 2a00e97f..b759a02d 100644 --- a/user-service/src/main/resources/bootstrap.yml +++ b/user-service/src/main/resources/bootstrap.yml @@ -9,7 +9,7 @@ spring: cloud: config: uri: http://localhost:8888 - name: database-user,redis, jwt, smtp + name: database-user,redis, jwt, smtp, oauth-kakao kafka: bootstrap-servers: "localhost:9092" consumer: From 94688275af04ef13a080ea1b949eeb17c6570e0a Mon Sep 17 00:00:00 2001 From: DDonghyeo Date: Thu, 30 May 2024 19:19:00 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=A8feat=20:=20OAuth=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=9A=A9=20Account=20Service=20&=20Converter?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/converter/AccountConverter.java | 11 +++ .../userservice/service/AccountsService.java | 94 +++++++++++++------ 2 files changed, 76 insertions(+), 29 deletions(-) diff --git a/user-service/src/main/java/com/waither/userservice/dto/converter/AccountConverter.java b/user-service/src/main/java/com/waither/userservice/dto/converter/AccountConverter.java index 9cbef719..5a63f005 100644 --- a/user-service/src/main/java/com/waither/userservice/dto/converter/AccountConverter.java +++ b/user-service/src/main/java/com/waither/userservice/dto/converter/AccountConverter.java @@ -1,6 +1,7 @@ package com.waither.userservice.dto.converter; import com.waither.userservice.dto.request.AccountReqDto; +import com.waither.userservice.dto.response.KakaoResDto; import com.waither.userservice.entity.User; import com.waither.userservice.entity.type.UserStatus; @@ -17,5 +18,15 @@ public static User toUser(AccountReqDto.RegisterRequestDto requestDto, String en .build(); } + public static User toUser(KakaoResDto.UserInfoResponseDto userInfo) { + return User.builder() + .authId(userInfo.getId()) + .nickname(userInfo.getKakaoAccount().getProfile().getNickName()) + .email(userInfo.getKakaoAccount().getEmail()) + .status(UserStatus.ACTIVE) + .custom(true) + .role("ROLE_USER") + .build(); + } } diff --git a/user-service/src/main/java/com/waither/userservice/service/AccountsService.java b/user-service/src/main/java/com/waither/userservice/service/AccountsService.java index ff385b33..1adc61f3 100644 --- a/user-service/src/main/java/com/waither/userservice/service/AccountsService.java +++ b/user-service/src/main/java/com/waither/userservice/service/AccountsService.java @@ -2,10 +2,12 @@ import com.waither.userservice.dto.converter.AccountConverter; import com.waither.userservice.dto.request.AccountReqDto; +import com.waither.userservice.dto.response.KakaoResDto; import com.waither.userservice.entity.Region; import com.waither.userservice.entity.Setting; import com.waither.userservice.entity.User; import com.waither.userservice.jwt.dto.JwtDto; +import com.waither.userservice.jwt.userdetails.PrincipalDetails; import com.waither.userservice.jwt.util.JwtUtil; import com.waither.userservice.repository.SettingRepository; import com.waither.userservice.util.RedisUtil; @@ -55,38 +57,44 @@ public void signup(AccountReqDto.RegisterRequestDto requestDto) { User newUser = AccountConverter.toUser(requestDto, encodedPw); // Setting을 기본값으로 설정 - Setting defaultSetting = Setting.builder() - .climateAlert(true) - .userAlert(true) - .snowAlert(true) - .windAlert(true) - .windDegree(10) - .regionReport(true) - .precipitation(true) - .wind(true) - .dust(true) - .weight(0.0) - .build(); + Setting defaultSetting = deafultSettings(); // Region 기본값으로 설정 - Region defaultRegion = Region.builder() - .regionName("서울시") - .longitude(37.5665) - .latitude(126.9780) - .build(); + Region defaultRegion = deafultRegion(); + + defaultSetting.setRegion(defaultRegion); + newUser.setSetting(defaultSetting); + userRepository.save(newUser); + } + + // 회원가입 (카카오) + public void signup(KakaoResDto.UserInfoResponseDto userInfo) { + + User newUser = AccountConverter.toUser(userInfo); + + Setting defaultSetting = deafultSettings(); + Region defaultRegion = deafultRegion(); defaultSetting.setRegion(defaultRegion); newUser.setSetting(defaultSetting); userRepository.save(newUser); } + // OAuth용 토큰 발급 + public JwtDto provideTokenForOAuth(String email) { + PrincipalDetails principalDetails = new PrincipalDetails(email, null, "ROLE_USER"); + return new JwtDto( + jwtUtil.createJwtAccessToken(principalDetails), + jwtUtil.createJwtRefreshToken(principalDetails)); + } + // 재발급 public JwtDto reissueToken(String refreshToken) { jwtUtil.isRefreshToken(refreshToken); return jwtUtil.reissueToken(refreshToken); } - // 인증 번호 전송 + public void sendAuthCodeToEmail(String email) { this.checkDuplicatedEmail(email); @@ -99,8 +107,8 @@ public void sendAuthCodeToEmail(String email) { redisUtil.save(AUTH_CODE_PREFIX + email, authCode, authCodeExpirationMillis, TimeUnit.MILLISECONDS); } - // 임시 비밀번호 보내기 + public String sendTempPassword(String email) { // 전송 String tempPassword = this.createTemporaryPassword(); @@ -109,24 +117,29 @@ public String sendTempPassword(String email) { return tempPassword; } - // 회원가입 하려는 이메일로 이미 가입한 회원이 있는지 확인하는 메서드. // 만약 해당 이메일을 가진 회원이 존재하면 예외를 발생. + private void checkDuplicatedEmail(String email) { Optional user = userRepository.findByEmail(email); if (user.isPresent()) { throw new CustomException(ErrorCode.USER_ALREADY_EXIST); } } + public boolean isUserRegistered(String email) { + Optional user = userRepository.findByEmail(email); + return user.isPresent(); + } // 회원 존재하는 지 확인 + public void checkUserExists(String email) { if (!userRepository.existsByEmail(email)) { throw new CustomException(ErrorCode.USER_NOT_FOUND); } } - // 인증 번호 생성하는 메서드 + public String createAuthCode() { int lenth = 6; try { @@ -141,8 +154,8 @@ public String createAuthCode() { throw new CustomException(ErrorCode.NO_SUCH_ALGORITHM); } } - //랜덤함수로 임시비밀번호 구문 만들기 + public String createTemporaryPassword() { char[] charSet = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; @@ -153,8 +166,8 @@ public String createTemporaryPassword() { .mapToObj(idx -> String.valueOf(charSet[idx])) .collect(Collectors.joining()); } - // 인증 코드를 검증하는 메서드. + public void verifyCode(String email, String authCode) { this.checkDuplicatedEmail(email); @@ -173,8 +186,8 @@ else if (redisUtil.get(AUTH_CODE_PREFIX + email).toString().equals(authCode)) { throw new CustomException(ErrorCode.INVALID_CODE); } } - // signup 과정 전에 인증된 email인지 확인하는 메서드 + public boolean verifiedAccounts(String email) { // 인증하지 않았거나, 인증 완료까지는 했지만 너무 시간이 경과한 경우 if(!redisUtil.hasKey(VERIFIED_PREFIX + email)) { @@ -183,22 +196,22 @@ public boolean verifiedAccounts(String email) { // hasKey(VERIFIED_PREFIX + email) 만 통과 하면 -> 인증 완료한 것 return true; } - // 임시 비밀번호로 비밀번호를 변경 + public void changeToTempPassword(String email, String tempPassword) { // 이메일로 사용자 조회 userRepository.findByEmail(email).get().setPassword(passwordEncoder.encode(tempPassword)); } - // 현재 비밀번호 체크 + public void checkPassword(User user, String currentPassword) { // 현재 비밀번호가 일치하는지 확인 if (!passwordEncoder.matches(currentPassword, user.getPassword())) { throw new CustomException(ErrorCode.CURRENT_PASSWORD_NOT_EQUAL); } } - // 비밀번호 변경 + public void updatePassword(User user, String newPassword) { if (passwordEncoder.matches(newPassword, user.getPassword())) { throw new CustomException(ErrorCode.CURRENT_PASSWORD_EQUAL); @@ -206,15 +219,38 @@ public void updatePassword(User user, String newPassword) { user.setPassword(passwordEncoder.encode(newPassword)); userRepository.save(user); } - // 닉네임 변경 + public void updateNickname(User user, String nickanme) { user.setNickname(nickanme); userRepository.save(user); } - // 회원 삭제 + public void deleteUser(User user){ userRepository.delete(user); } + + private Setting deafultSettings() { + return Setting.builder() + .climateAlert(true) + .userAlert(true) + .snowAlert(true) + .windAlert(true) + .windDegree(10) + .regionReport(true) + .precipitation(true) + .wind(true) + .dust(true) + .weight(0.0) + .build(); + } + + private Region deafultRegion() { + return Region.builder() + .regionName("서울시") + .longitude(37.5665) + .latitude(126.9780) + .build(); + } } From a837b29e85ddd773468e4f18f68b7a8e0d0f824a Mon Sep 17 00:00:00 2001 From: DDonghyeo Date: Thu, 30 May 2024 19:19:39 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E2=99=BB=EF=B8=8Frefactor=20:=20uriBuilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../userservice/service/KakaoService.java | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/user-service/src/main/java/com/waither/userservice/service/KakaoService.java b/user-service/src/main/java/com/waither/userservice/service/KakaoService.java index b5ff8bb6..6879ff0d 100644 --- a/user-service/src/main/java/com/waither/userservice/service/KakaoService.java +++ b/user-service/src/main/java/com/waither/userservice/service/KakaoService.java @@ -13,30 +13,31 @@ import reactor.core.publisher.Mono; @Slf4j -@Service @RequiredArgsConstructor +@Service public class KakaoService { - private String client_id; - private final String KATUH_TOKEN_URL; - private final String KAUTH_USER_URL; + private String clientId; + private final String KAUTH_TOKEN_URL_HOST ; + private final String KAUTH_USER_URL_HOST; @Autowired - public KakaoService(@Value("${kakao.client_id}") String client_id) { - this.client_id = client_id; - this.KATUH_TOKEN_URL = "https://kauth.kakao.com/oauth/token"; - this.KAUTH_USER_URL = "https://kapi.kakao.com/v2/user/me"; + public KakaoService(@Value("${kakao.client_id}") String clientId) { + this.clientId = clientId; + KAUTH_TOKEN_URL_HOST ="https://kauth.kakao.com"; + KAUTH_USER_URL_HOST = "https://kapi.kakao.com"; } public String getAccessTokenFromKakao(String code) { - KakaoResDto.TokenResponseDto kakaoTokenResponseDto = WebClient.create().post() + KakaoResDto.TokenResponseDto kakaoTokenResponseDto = WebClient.create(KAUTH_TOKEN_URL_HOST).post() .uri(uriBuilder -> uriBuilder - .path(KATUH_TOKEN_URL) + .scheme("https") + .path("/oauth/token") .queryParam("grant_type", "authorization_code") - .queryParam("client_id", client_id) + .queryParam("client_id", clientId) .queryParam("code", code) - .build()) + .build(true)) .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) .retrieve() //TODO : Custom Exception @@ -46,12 +47,6 @@ public String getAccessTokenFromKakao(String code) { .block(); -// log.info(" [Kakao Service] Access Token ------> {}", kakaoTokenResponseDto.getAccessToken()); -// log.info(" [Kakao Service] Refresh Token ------> {}", kakaoTokenResponseDto.getRefreshToken()); - //제공 조건: OpenID Connect가 활성화 된 앱의 토큰 발급 요청인 경우 또는 scope에 openid를 포함한 추가 항목 동의 받기 요청을 거친 토큰 발급 요청인 경우 -// log.info(" [Kakao Service] Id Token ------> {}", kakaoTokenResponseDto.getIdToken()); -// log.info(" [Kakao Service] Scope ------> {}", kakaoTokenResponseDto.getScope()); - return kakaoTokenResponseDto.getAccessToken(); } @@ -60,11 +55,12 @@ public String getAccessTokenFromKakao(String code) { public KakaoResDto.UserInfoResponseDto getUserInfo(String accessToken) { - KakaoResDto.UserInfoResponseDto kakaoUserInfo = WebClient.create() + KakaoResDto.UserInfoResponseDto userInfo = WebClient.create(KAUTH_USER_URL_HOST) .get() .uri(uriBuilder -> uriBuilder - .path(KAUTH_USER_URL) - .build()) + .scheme("https") + .path("/v2/user/me") + .build(true)) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) // access token 인가 .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) .retrieve() @@ -74,10 +70,6 @@ public KakaoResDto.UserInfoResponseDto getUserInfo(String accessToken) { .bodyToMono(KakaoResDto.UserInfoResponseDto.class) .block(); -// log.info("[ Kakao Service ] Auth ID ---> {} ", kakaoUserInfo.getId()); -// log.info("[ Kakao Service ] NickName ---> {} ", kakaoUserInfo.getKakaoAccount().getProfile().getNickName()); -// log.info("[ Kakao Service ] ProfileImageUrl ---> {} ", kakaoUserInfo.getKakaoAccount().getProfile().getProfileImageUrl()); - - return kakaoUserInfo; + return userInfo; } } From b990e1e1d0b70619e68160c74c4587a61f3f77f4 Mon Sep 17 00:00:00 2001 From: DDonghyeo Date: Thu, 30 May 2024 19:19:59 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E2=99=BB=EF=B8=8Frefactor=20:=20uri=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20&=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../userservice/controller/OAuthController.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/user-service/src/main/java/com/waither/userservice/controller/OAuthController.java b/user-service/src/main/java/com/waither/userservice/controller/OAuthController.java index 6483c13a..97192b53 100644 --- a/user-service/src/main/java/com/waither/userservice/controller/OAuthController.java +++ b/user-service/src/main/java/com/waither/userservice/controller/OAuthController.java @@ -13,24 +13,25 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/oauth") +@RequestMapping("/user/oauth") public class OAuthController { private final KakaoService kakaoService; private final AccountsService accountsService; - @GetMapping("/callback") + @GetMapping("/kakao/callback") public ApiResponse callback(@RequestParam("code") String code) { String accessTokenFromKakao = kakaoService.getAccessTokenFromKakao(code); KakaoResDto.UserInfoResponseDto userInfo = kakaoService.getUserInfo(accessTokenFromKakao); - //TODO: if 회원가입 안 한 상태 -> 회원가입 진행 + String email = userInfo.getKakaoAccount().getEmail(); + if (!accountsService.isUserRegistered(email)) { + accountsService.signup(userInfo); + } - //TODO: 토큰 발급 - //accountsService - return ApiResponse.onSuccess(null); + return ApiResponse.onSuccess(accountsService.provideTokenForOAuth(email)); } } From 0643c7e2eb7fa9a1fec94e5655a704abf54c11db Mon Sep 17 00:00:00 2001 From: DDonghyeo Date: Thu, 30 May 2024 19:20:27 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E2=99=BB=EF=B8=8Frefactor=20:=20AuthId=20&?= =?UTF-8?q?=20AuthType=20=EC=B6=94=EA=B0=80,password=20nullable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/waither/userservice/entity/User.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/user-service/src/main/java/com/waither/userservice/entity/User.java b/user-service/src/main/java/com/waither/userservice/entity/User.java index b4807a99..0ee58e72 100644 --- a/user-service/src/main/java/com/waither/userservice/entity/User.java +++ b/user-service/src/main/java/com/waither/userservice/entity/User.java @@ -1,5 +1,6 @@ package com.waither.userservice.entity; +import com.waither.userservice.entity.type.AuthType; import com.waither.userservice.entity.type.UserStatus; import com.waither.userservice.global.BaseEntity; import jakarta.persistence.*; @@ -17,18 +18,26 @@ public class User extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + // OAuth ID + @Column(name = "auth_id") + private Long authId; + // 유저 이메일 @Column(name = "email", nullable = false, unique = true) private String email; // 유저 비밀번호 - @Column(name = "password", nullable = false) + @Column(name = "password") private String password; // 유저 닉네임 @Column(name = "nickname", nullable = false) private String nickname; + // 회원 가입 타입 + @Enumerated(EnumType.STRING) + private AuthType authType; + // 유저 상태 (active / 휴면 / 탈퇴 등) @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false) From c22bb041d0cb719748ab08f297c58e0b29ae37b2 Mon Sep 17 00:00:00 2001 From: DDonghyeo Date: Thu, 30 May 2024 19:20:49 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=A8feat=20:=20AuthType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/waither/userservice/entity/type/AuthType.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 user-service/src/main/java/com/waither/userservice/entity/type/AuthType.java diff --git a/user-service/src/main/java/com/waither/userservice/entity/type/AuthType.java b/user-service/src/main/java/com/waither/userservice/entity/type/AuthType.java new file mode 100644 index 00000000..29b5d019 --- /dev/null +++ b/user-service/src/main/java/com/waither/userservice/entity/type/AuthType.java @@ -0,0 +1,9 @@ +package com.waither.userservice.entity.type; + +public enum AuthType { + + EMAIL, + KAKAO, + GOOGLE, + APPLE; +}