diff --git a/moonshot-api/src/main/java/org/moonshot/user/controller/UserController.java b/moonshot-api/src/main/java/org/moonshot/user/controller/UserController.java index 0f7586bc..8b1105f7 100644 --- a/moonshot-api/src/main/java/org/moonshot/user/controller/UserController.java +++ b/moonshot-api/src/main/java/org/moonshot/user/controller/UserController.java @@ -1,9 +1,6 @@ package org.moonshot.user.controller; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; -import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.moonshot.jwt.TokenResponse; @@ -16,7 +13,6 @@ import org.moonshot.user.dto.response.UserInfoResponse; import org.moonshot.user.model.LoginUser; import org.moonshot.user.service.UserService; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -38,7 +34,7 @@ public class UserController implements UserApi { @PostMapping("/login") @Logging(item = "User", action = "Post") public ResponseEntity> login(@RequestHeader("Authorization") final String authorization, - @RequestBody final SocialLoginRequest socialLoginRequest) throws IOException { + @RequestBody final SocialLoginRequest socialLoginRequest) { return ResponseEntity.ok(MoonshotResponse.success(SuccessType.POST_LOGIN_SUCCESS, userService.login(SocialLoginRequest.of(socialLoginRequest.socialPlatform(), authorization)))); } diff --git a/moonshot-api/src/main/java/org/moonshot/user/service/UserService.java b/moonshot-api/src/main/java/org/moonshot/user/service/UserService.java index 6aa18eda..0db33b60 100644 --- a/moonshot-api/src/main/java/org/moonshot/user/service/UserService.java +++ b/moonshot-api/src/main/java/org/moonshot/user/service/UserService.java @@ -1,40 +1,27 @@ package org.moonshot.user.service; import static org.moonshot.response.ErrorType.NOT_FOUND_USER; +import static org.moonshot.response.ErrorType.NOT_SUPPORTED_LOGIN_PLATFORM; import static org.moonshot.user.service.validator.UserValidator.hasChange; -import static org.moonshot.user.service.validator.UserValidator.isNewUser; import static org.moonshot.user.service.validator.UserValidator.validateUserAuthorization; -import static org.moonshot.util.MDCUtil.USER_REQUEST_ORIGIN; -import static org.moonshot.util.MDCUtil.get; import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.moonshot.discord.SignUpEvent; +import org.moonshot.exception.BadRequestException; import org.moonshot.exception.NotFoundException; import org.moonshot.jwt.JwtTokenProvider; import org.moonshot.jwt.TokenResponse; import org.moonshot.objective.service.ObjectiveService; -import org.moonshot.openfeign.dto.response.google.GoogleInfoResponse; -import org.moonshot.openfeign.dto.response.google.GoogleTokenResponse; -import org.moonshot.openfeign.dto.response.kakao.KakaoTokenResponse; -import org.moonshot.openfeign.dto.response.kakao.KakaoUserResponse; -import org.moonshot.openfeign.google.GoogleApiClient; -import org.moonshot.openfeign.google.GoogleAuthApiClient; -import org.moonshot.openfeign.kakao.KakaoApiClient; -import org.moonshot.openfeign.kakao.KakaoAuthApiClient; import org.moonshot.user.dto.request.SocialLoginRequest; import org.moonshot.user.dto.request.UserInfoRequest; import org.moonshot.user.dto.response.SocialLoginResponse; import org.moonshot.user.dto.response.UserInfoResponse; import org.moonshot.user.model.User; import org.moonshot.user.repository.UserRepository; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; +import org.moonshot.user.service.social.SocialLoginContext; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Slf4j @@ -43,95 +30,16 @@ @RequiredArgsConstructor public class UserService { - @Value("${google.client-id}") - private String googleClientId; - - @Value("${google.client-secret}") - private String googleClientSecret; - - @Value("${google.redirect-url}") - private String googleRedirectUrl; - - @Value("${kakao.client-id}") - private String kakaoClientId; - - @Value("${kakao.redirect-uri}") - private String kakaoRedirectUri; - private final UserRepository userRepository; private final ObjectiveService objectiveService; - private final ApplicationEventPublisher eventPublisher; - - private final GoogleAuthApiClient googleAuthApiClient; - private final GoogleApiClient googleApiClient; - private final KakaoAuthApiClient kakaoAuthApiClient; - private final KakaoApiClient kakaoApiClient; private final JwtTokenProvider jwtTokenProvider; + private final SocialLoginContext socialLoginContext; public SocialLoginResponse login(final SocialLoginRequest request) { - return switch (request.socialPlatform().getValue()) { - case "google" -> googleLogin(request); - case "kakao" -> kakaoLogin(request); - default -> null; - }; - } - - public SocialLoginResponse googleLogin(final SocialLoginRequest request) { - GoogleTokenResponse tokenResponse = googleAuthApiClient.googleAuth( - request.code(), - googleClientId, - googleClientSecret, - googleRedirectUrl, - "authorization_code" - ); - GoogleInfoResponse userResponse = googleApiClient.googleInfo("Bearer " + tokenResponse.accessToken()); - Optional findUser = userRepository.findUserBySocialId(userResponse.sub()); - User user; - if (isNewUser(findUser)) { - User newUser = userRepository.save(User.builderWithSignIn() - .socialId(userResponse.sub()) - .socialPlatform(request.socialPlatform()) - .name(userResponse.name()) - .imageUrl(userResponse.picture()) - .email(userResponse.email()) - .build()); - user = newUser; - publishSignUpEvent(newUser); - } else { - user = findUser.get(); - user.resetDeleteAt(); - } - TokenResponse token = new TokenResponse(jwtTokenProvider.generateAccessToken(user.getId()), jwtTokenProvider.generateRefreshToken(user.getId())); - return SocialLoginResponse.of(user.getId(), user.getName(), token); - } - - public SocialLoginResponse kakaoLogin(final SocialLoginRequest request) { - KakaoTokenResponse tokenResponse = kakaoAuthApiClient.getOAuth2AccessToken( - "authorization_code", - kakaoClientId, - (String)get(USER_REQUEST_ORIGIN) + kakaoRedirectUri, - request.code() - ); - KakaoUserResponse userResponse = kakaoApiClient.getUserInformation( - "Bearer " + tokenResponse.accessToken()); - Optional findUser = userRepository.findUserBySocialId(userResponse.id()); - User user; - if (isNewUser(findUser)) { - User newUser = userRepository.save(User.builderWithSignIn() - .socialId(userResponse.id()) - .socialPlatform(request.socialPlatform()) - .name(userResponse.kakaoAccount().profile().nickname()) - .imageUrl(userResponse.kakaoAccount().profile().profileImageUrl()) - .email(null) - .build()); - user = newUser; - publishSignUpEvent(newUser); - } else { - user = findUser.get(); - user.resetDeleteAt(); + if (socialLoginContext.support(request.socialPlatform())) { + return socialLoginContext.doLogin(request); } - TokenResponse token = new TokenResponse(jwtTokenProvider.generateAccessToken(user.getId()), jwtTokenProvider.generateRefreshToken(user.getId())); - return SocialLoginResponse.of(user.getId(), user.getName(), token); + throw new BadRequestException(NOT_SUPPORTED_LOGIN_PLATFORM); } public TokenResponse reissue(final String refreshToken) { @@ -181,17 +89,6 @@ public void updateUserProfileImage(final Long userId, final String imageUrl) { user.modifyProfileImage(imageUrl); } - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void publishSignUpEvent(final User user) { - eventPublisher.publishEvent(SignUpEvent.of( - user.getName(), - user.getEmail() == null ? "" : user.getEmail(), - user.getSocialPlatform().toString(), - LocalDateTime.now(), - user.getImageUrl() - )); - } - public void softDeleteUser(LocalDateTime currentDate) { List expiredUserList = userRepository.findIdByDeletedAtBefore(currentDate); if(!expiredUserList.isEmpty()) { diff --git a/moonshot-api/src/main/java/org/moonshot/user/service/social/GoogleLoginStrategy.java b/moonshot-api/src/main/java/org/moonshot/user/service/social/GoogleLoginStrategy.java new file mode 100644 index 00000000..8ab271af --- /dev/null +++ b/moonshot-api/src/main/java/org/moonshot/user/service/social/GoogleLoginStrategy.java @@ -0,0 +1,93 @@ +package org.moonshot.user.service.social; + +import static org.moonshot.user.service.validator.UserValidator.isNewUser; + +import java.time.LocalDateTime; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.moonshot.discord.SignUpEvent; +import org.moonshot.jwt.JwtTokenProvider; +import org.moonshot.jwt.TokenResponse; +import org.moonshot.openfeign.dto.response.google.GoogleInfoResponse; +import org.moonshot.openfeign.dto.response.google.GoogleTokenResponse; +import org.moonshot.openfeign.google.GoogleApiClient; +import org.moonshot.openfeign.google.GoogleAuthApiClient; +import org.moonshot.user.dto.request.SocialLoginRequest; +import org.moonshot.user.dto.response.SocialLoginResponse; +import org.moonshot.user.model.User; +import org.moonshot.user.repository.UserRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class GoogleLoginStrategy implements SocialLoginStrategy { + + @Value("${google.client-id}") + private String googleClientId; + + @Value("${google.client-secret}") + private String googleClientSecret; + + @Value("${google.redirect-url}") + private String googleRedirectUrl; + + private final GoogleAuthApiClient googleAuthApiClient; + private final GoogleApiClient googleApiClient; + + private final ApplicationEventPublisher eventPublisher; + private final JwtTokenProvider jwtTokenProvider; + private final UserRepository userRepository; + + @Override + @Transactional + public SocialLoginResponse login(SocialLoginRequest request) { + GoogleTokenResponse tokenResponse = googleAuthApiClient.googleAuth( + request.code(), + googleClientId, + googleClientSecret, + googleRedirectUrl, + "authorization_code" + ); + GoogleInfoResponse userResponse = googleApiClient.googleInfo("Bearer " + tokenResponse.accessToken()); + Optional findUser = userRepository.findUserBySocialId(userResponse.sub()); + User user; + if (isNewUser(findUser)) { + User newUser = userRepository.save(User.builderWithSignIn() + .socialId(userResponse.sub()) + .socialPlatform(request.socialPlatform()) + .name(userResponse.name()) + .imageUrl(userResponse.picture()) + .email(userResponse.email()) + .build()); + user = newUser; + publishSignUpEvent(newUser); + } else { + user = findUser.get(); + user.resetDeleteAt(); + } + TokenResponse token = new TokenResponse(jwtTokenProvider.generateAccessToken(user.getId()), jwtTokenProvider.generateRefreshToken(user.getId())); + return SocialLoginResponse.of(user.getId(), user.getName(), token); + } + + @Override + public boolean support(String provider) { + return provider.equals("GOOGLE"); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void publishSignUpEvent(User user) { + eventPublisher.publishEvent(SignUpEvent.of( + user.getName(), + user.getEmail() == null ? "" : user.getEmail(), + user.getSocialPlatform().toString(), + LocalDateTime.now(), + user.getImageUrl() + )); + } + +} diff --git a/moonshot-api/src/main/java/org/moonshot/user/service/social/KakaoLoginStrategy.java b/moonshot-api/src/main/java/org/moonshot/user/service/social/KakaoLoginStrategy.java new file mode 100644 index 00000000..60a22d92 --- /dev/null +++ b/moonshot-api/src/main/java/org/moonshot/user/service/social/KakaoLoginStrategy.java @@ -0,0 +1,92 @@ +package org.moonshot.user.service.social; + +import static org.moonshot.user.service.validator.UserValidator.isNewUser; +import static org.moonshot.util.MDCUtil.USER_REQUEST_ORIGIN; +import static org.moonshot.util.MDCUtil.get; + +import java.time.LocalDateTime; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.moonshot.discord.SignUpEvent; +import org.moonshot.jwt.JwtTokenProvider; +import org.moonshot.jwt.TokenResponse; +import org.moonshot.openfeign.dto.response.kakao.KakaoTokenResponse; +import org.moonshot.openfeign.dto.response.kakao.KakaoUserResponse; +import org.moonshot.openfeign.kakao.KakaoApiClient; +import org.moonshot.openfeign.kakao.KakaoAuthApiClient; +import org.moonshot.user.dto.request.SocialLoginRequest; +import org.moonshot.user.dto.response.SocialLoginResponse; +import org.moonshot.user.model.User; +import org.moonshot.user.repository.UserRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class KakaoLoginStrategy implements SocialLoginStrategy { + + @Value("${kakao.client-id}") + private String kakaoClientId; + + @Value("${kakao.redirect-uri}") + private String kakaoRedirectUri; + + private final KakaoAuthApiClient kakaoAuthApiClient; + private final KakaoApiClient kakaoApiClient; + + private final ApplicationEventPublisher eventPublisher; + private final JwtTokenProvider jwtTokenProvider; + private final UserRepository userRepository; + + @Override + @Transactional + public SocialLoginResponse login(SocialLoginRequest request) { + KakaoTokenResponse tokenResponse = kakaoAuthApiClient.getOAuth2AccessToken( + "authorization_code", + kakaoClientId, + (String)get(USER_REQUEST_ORIGIN) + kakaoRedirectUri, + request.code() + ); + KakaoUserResponse userResponse = kakaoApiClient.getUserInformation( + "Bearer " + tokenResponse.accessToken()); + Optional findUser = userRepository.findUserBySocialId(userResponse.id()); + User user; + if (isNewUser(findUser)) { + User newUser = userRepository.save(User.builderWithSignIn() + .socialId(userResponse.id()) + .socialPlatform(request.socialPlatform()) + .name(userResponse.kakaoAccount().profile().nickname()) + .imageUrl(userResponse.kakaoAccount().profile().profileImageUrl()) + .email(null) + .build()); + user = newUser; + publishSignUpEvent(newUser); + } else { + user = findUser.get(); + user.resetDeleteAt(); + } + TokenResponse token = new TokenResponse(jwtTokenProvider.generateAccessToken(user.getId()), jwtTokenProvider.generateRefreshToken(user.getId())); + return SocialLoginResponse.of(user.getId(), user.getName(), token); + } + + @Override + public boolean support(String provider) { + return provider.equals("KAKAO"); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void publishSignUpEvent(User user) { + eventPublisher.publishEvent(SignUpEvent.of( + user.getName(), + user.getEmail() == null ? "" : user.getEmail(), + user.getSocialPlatform().toString(), + LocalDateTime.now(), + user.getImageUrl() + )); + } + +} diff --git a/moonshot-api/src/main/java/org/moonshot/user/service/social/SocialLoginContext.java b/moonshot-api/src/main/java/org/moonshot/user/service/social/SocialLoginContext.java new file mode 100644 index 00000000..8c3d8521 --- /dev/null +++ b/moonshot-api/src/main/java/org/moonshot/user/service/social/SocialLoginContext.java @@ -0,0 +1,47 @@ +package org.moonshot.user.service.social; + +import static org.moonshot.response.ErrorType.NOT_SUPPORTED_LOGIN_PLATFORM; + +import jakarta.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.moonshot.exception.BadRequestException; +import org.moonshot.user.dto.request.SocialLoginRequest; +import org.moonshot.user.dto.response.SocialLoginResponse; +import org.moonshot.user.model.SocialPlatform; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class SocialLoginContext { + + private final GoogleLoginStrategy googleLoginStrategy; + private final KakaoLoginStrategy kakaoLoginStrategy; + private final List socialLoginStrategies = new ArrayList<>(); + + @PostConstruct + void initSocialLoginContext() { + socialLoginStrategies.add(googleLoginStrategy); + socialLoginStrategies.add(kakaoLoginStrategy); + } + + public boolean support(SocialPlatform platform) { + for (SocialLoginStrategy strategy : socialLoginStrategies) { + if (strategy.support(platform.toString())) { + return true; + } + } + return false; + } + + public SocialLoginResponse doLogin(final SocialLoginRequest request) { + for (SocialLoginStrategy strategy : socialLoginStrategies) { + if (strategy.support(request.socialPlatform().toString())) { + return strategy.login(request); + } + } + throw new BadRequestException(NOT_SUPPORTED_LOGIN_PLATFORM); + } + +} diff --git a/moonshot-api/src/main/java/org/moonshot/user/service/social/SocialLoginStrategy.java b/moonshot-api/src/main/java/org/moonshot/user/service/social/SocialLoginStrategy.java new file mode 100644 index 00000000..9440794e --- /dev/null +++ b/moonshot-api/src/main/java/org/moonshot/user/service/social/SocialLoginStrategy.java @@ -0,0 +1,13 @@ +package org.moonshot.user.service.social; + +import org.moonshot.user.dto.request.SocialLoginRequest; +import org.moonshot.user.dto.response.SocialLoginResponse; +import org.moonshot.user.model.User; + +public interface SocialLoginStrategy { + + SocialLoginResponse login(final SocialLoginRequest request); + boolean support(String provider); + void publishSignUpEvent(final User user); + +} diff --git a/moonshot-common/src/main/java/org/moonshot/constants/UserConstants.java b/moonshot-common/src/main/java/org/moonshot/constants/UserConstants.java new file mode 100644 index 00000000..34fe372b --- /dev/null +++ b/moonshot-common/src/main/java/org/moonshot/constants/UserConstants.java @@ -0,0 +1,7 @@ +package org.moonshot.constants; + +public class UserConstants { + + public static final Long USER_RETENTION_PERIOD = 14L; + +} diff --git a/moonshot-common/src/main/java/org/moonshot/response/ErrorType.java b/moonshot-common/src/main/java/org/moonshot/response/ErrorType.java index 3944cba9..dbe63cc0 100644 --- a/moonshot-common/src/main/java/org/moonshot/response/ErrorType.java +++ b/moonshot-common/src/main/java/org/moonshot/response/ErrorType.java @@ -30,6 +30,7 @@ public enum ErrorType { INVALID_KEY_RESULT_PERIOD(HttpStatus.BAD_REQUEST, 4016, "KeyResult 기간 설정이 올바르지 않습니다."), REQUIRED_EXPIRE_AT(HttpStatus.BAD_REQUEST, 4017, "기간 연장시 목표 종료 기간은 필수 입력값입니다."), REQUIRED_KEY_RESULT_VALUE(HttpStatus.BAD_REQUEST, 4018, "KR 수정시 목표값과 체크인 로그는 필수 입력값입니다."), + NOT_SUPPORTED_LOGIN_PLATFORM(HttpStatus.BAD_REQUEST, 4019, "지원하지 않는 소셜 로그인 플랫폼입니다."), /** * 401 UNAUTHROZIED (4100 ~ 4199) diff --git a/moonshot-domain/src/main/java/org/moonshot/user/model/User.java b/moonshot-domain/src/main/java/org/moonshot/user/model/User.java index f35516be..f2733f76 100644 --- a/moonshot-domain/src/main/java/org/moonshot/user/model/User.java +++ b/moonshot-domain/src/main/java/org/moonshot/user/model/User.java @@ -1,5 +1,7 @@ package org.moonshot.user.model; +import static org.moonshot.constants.UserConstants.USER_RETENTION_PERIOD; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -21,9 +23,6 @@ @Builder(builderMethodName = "buildWithId") public class User { - // TODO 기획 측 약관 확정 이후 수정 필요 - private static final Long USER_RETENTION_PERIOD = 14L; - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id")