diff --git a/src/main/java/com/moim/backend/domain/user/controller/UserController.java b/src/main/java/com/moim/backend/domain/user/controller/UserController.java index 9e0e6be..f1e0cf5 100644 --- a/src/main/java/com/moim/backend/domain/user/controller/UserController.java +++ b/src/main/java/com/moim/backend/domain/user/controller/UserController.java @@ -31,6 +31,14 @@ public CustomResponseEntity loginByOAuth( return CustomResponseEntity.success(userService.loginByOAuth(code, platform)); } + // 소셜 로그인 API + @GetMapping("/signin/token") + public CustomResponseEntity socialLoginByAccessToken( + @RequestParam(name = "token") String token, @RequestParam Platform platform + ) { + return CustomResponseEntity.success(userService.loginByAccessToken(token, platform)); + } + // 엑세스 토큰 재발급 @GetMapping("/refresh") public CustomResponseEntity refreshAccessToken( diff --git a/src/main/java/com/moim/backend/domain/user/service/GoogleLoginService.java b/src/main/java/com/moim/backend/domain/user/service/GoogleLoginService.java index fa3dbde..1496838 100644 --- a/src/main/java/com/moim/backend/domain/user/service/GoogleLoginService.java +++ b/src/main/java/com/moim/backend/domain/user/service/GoogleLoginService.java @@ -47,6 +47,16 @@ public Users toEntityUser(String code, Platform platform) { .build(); } + @Override + public Users toEntityUserByToken(String accessToken) { + GoogleUserResponse profile = getGoogleUser(accessToken); + + return Users.builder() + .email(profile.getEmail()) + .name(profile.getName()) + .build(); + } + // Google AccessToken 반환 private String getGoogleAccessToken(String decodedCode) { return toRequestGoogleToken(decodedCode).getAccessToken(); diff --git a/src/main/java/com/moim/backend/domain/user/service/KakaoLoginService.java b/src/main/java/com/moim/backend/domain/user/service/KakaoLoginService.java index dc931e7..48d0f32 100644 --- a/src/main/java/com/moim/backend/domain/user/service/KakaoLoginService.java +++ b/src/main/java/com/moim/backend/domain/user/service/KakaoLoginService.java @@ -43,6 +43,16 @@ public Users toEntityUser(String code, Platform platform) { .build(); } + @Override + public Users toEntityUserByToken(String accessToken) { + KakaoUserResponse profile = getKakaoUser(accessToken); + + return Users.builder() + .email(profile.getKakaoAccount().getEmail()) + .name(profile.getProperties().getNickname()) + .build(); + } + // Kakao AccessToken 응답 private String getKakaoAccessToken(String code) { HttpEntity httpEntity = createHttpEntityWithCode(code); diff --git a/src/main/java/com/moim/backend/domain/user/service/NaverLoginService.java b/src/main/java/com/moim/backend/domain/user/service/NaverLoginService.java index 234b78e..e3159f0 100644 --- a/src/main/java/com/moim/backend/domain/user/service/NaverLoginService.java +++ b/src/main/java/com/moim/backend/domain/user/service/NaverLoginService.java @@ -46,6 +46,16 @@ public Users toEntityUser(String code, Platform platform) { .build(); } + @Override + public Users toEntityUserByToken(String accessToken) { + NaverUserResponse.NaverUserDetail profile = getNaverUser(accessToken); + + return Users.builder() + .email(profile.getEmail()) + .name(profile.getName()) + .build(); + } + // Naver AccessToken 응답 private String getNaverAccessToken(String code) { NaverTokenResponse response = toRequestTokenNaverAccessToken(code); diff --git a/src/main/java/com/moim/backend/domain/user/service/OAuth2LoginService.java b/src/main/java/com/moim/backend/domain/user/service/OAuth2LoginService.java index 731a2a2..2cd5263 100644 --- a/src/main/java/com/moim/backend/domain/user/service/OAuth2LoginService.java +++ b/src/main/java/com/moim/backend/domain/user/service/OAuth2LoginService.java @@ -8,4 +8,6 @@ public interface OAuth2LoginService { Platform supports(); Users toEntityUser(String code, Platform platform); + + Users toEntityUserByToken(String accessToken); } diff --git a/src/main/java/com/moim/backend/domain/user/service/UserService.java b/src/main/java/com/moim/backend/domain/user/service/UserService.java index c1e233d..f858f3f 100644 --- a/src/main/java/com/moim/backend/domain/user/service/UserService.java +++ b/src/main/java/com/moim/backend/domain/user/service/UserService.java @@ -43,6 +43,22 @@ public UserLoginResponse loginByOAuth(String code, Platform platform) { return UserLoginResponse.response(user, accessToken, refreshToken); } + // 소셜 로그인 + @Transactional + public UserLoginResponse loginByAccessToken(String token, Platform platform) { + // 요청된 로그인 플랫폼 확인 후 소셜 로그인 진행 + Users userEntity = oauthLoginProcessByToken(token, platform); + + // 현재 서비스 내 회원인지 검증 및 save + Users user = saveOrUpdate(userEntity); + + // 서비스 JWT 토큰 발급 + String accessToken = jwtService.createAccessToken(user.getEmail()); + String refreshToken = jwtService.createRefreshToken(user.getEmail()); + + return UserLoginResponse.response(user, accessToken, refreshToken); + } + public UserReissueResponse reissueAccessToken(String refreshToken) { return UserReissueResponse.toResponse(jwtService.reissueAccessToken(refreshToken)); } @@ -61,6 +77,12 @@ private Users oauthLoginProcess(String code, Platform platform) { .orElseThrow(() -> new CustomException(FAIL_SOCIAL_LOGIN)); } + // method + private Users oauthLoginProcessByToken(String token, Platform platform) { + return getOptionalSocialUserEntityByToken(token, platform) + .orElseThrow(() -> new CustomException(FAIL_SOCIAL_LOGIN)); + } + private Optional getOptionalSocialUserEntity(String code, Platform platform) { for (OAuth2LoginService oAuth2LoginService : oAuth2LoginServices) { if (oAuth2LoginService.supports().equals(platform)) { @@ -70,6 +92,15 @@ private Optional getOptionalSocialUserEntity(String code, Platform platfo return Optional.empty(); } + private Optional getOptionalSocialUserEntityByToken(String accessToken, Platform platform) { + for (OAuth2LoginService oAuth2LoginService : oAuth2LoginServices) { + if (oAuth2LoginService.supports().equals(platform)) { + return Optional.of(oAuth2LoginService.toEntityUserByToken(accessToken)); + } + } + return Optional.empty(); + } + private Users saveOrUpdate(Users userEntity) { Users user = userRepository.findByEmail(userEntity.getEmail()) .map(entity -> entity.update(userEntity.getName())) diff --git a/src/test/java/com/moim/backend/docs/user/UserControllerDocsTest.java b/src/test/java/com/moim/backend/docs/user/UserControllerDocsTest.java index ac280c6..5ff55b3 100644 --- a/src/test/java/com/moim/backend/docs/user/UserControllerDocsTest.java +++ b/src/test/java/com/moim/backend/docs/user/UserControllerDocsTest.java @@ -28,6 +28,7 @@ public class UserControllerDocsTest extends RestDocsSupport { private final UserService userService = mock(UserService.class); + private final String code = "Hx-PXmWuFaGakYCEy8hkUIVOWUSXIOtD7cosKDSIKsiwodR1g35KXQQWX9H4hXlcpZ45eSgo3dGkWWWOSX-z9iQ"; private final String accessToken = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ5dS1qdW5nMzE0NzZAbmF2ZXIuY29tIiwiZXhwIjoxNjg5MjYwODM2fQ.cgZ8eFDU_Gz7Z3EghXxoa3v-iXUeQmBZ1AfKCBQZnnqFJ6mqMqGdiTS5uVCF1lIKBarXeD6nEmRZj9Ng94pnHw"; private final String refreshToken = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ5dS1qdW5nMzE0NzZAbmF2ZXIuY29tIiwiZXhwIjoxNjg5MjYwODM2fQ.cgZ8eFDU_Gz7Z3EghXxoa3v-iXUeQmBZ1AfKCBQZnnqFJ6mqMqGdiTS5uVCF1lIKBarXeD6nEmRZj9Ng94pnHw"; @@ -40,7 +41,7 @@ protected Object initController() { @Test void loginByOAuth() throws Exception { // given - given(userService.loginByOAuth("Hx-PXmWuFaGakYCEy8hkUIVOWUSXIOtD7cosKDSIKsiwodR1g35KXQQWX9H4hXlcpZ45eSgo3dGkWWWOSX-z9iQ", NAVER)) + given(userService.loginByOAuth(code, NAVER)) .willReturn( UserLoginResponse.builder() .userId(1L) @@ -81,6 +82,51 @@ void loginByOAuth() throws Exception { .andDo(document); } + @DisplayName("소셜 로그인 API") + @Test + void socialLoginByAccessToken() throws Exception { + // given + given(userService.loginByAccessToken(accessToken, NAVER)) + .willReturn( + UserLoginResponse.builder() + .userId(1L) + .email("moidots@gmail.com") + .name("모이닷") + .accessToken(accessToken) + .refreshToken(refreshToken) + .build() + ); + + MockHttpServletRequestBuilder httpRequest = RestDocumentationRequestBuilders.get("/auth/signin/token") + .param("token", accessToken) + .param("platform", "NAVER"); + + ResourceSnippetParameters parameters = ResourceSnippetParameters.builder() + .tag("유저 API") + .summary("소셜 로그인 API") + .queryParameters( + parameterWithName("token").description("소셜 로그인 redirect 엑세스 코드"), + parameterWithName("platform").description("플랫폼 : 'NAVER' / 'KAKAO' / 'GOOGLE' ")) + .responseFields( + fieldWithPath("code").type(NUMBER).description("상태 코드"), + fieldWithPath("message").type(STRING).description("상태 메세지"), + fieldWithPath("data.userId").type(NUMBER).description("유저 아이디"), + fieldWithPath("data.email").type(STRING).description("유저 이메일"), + fieldWithPath("data.name").type(STRING).description("유저 이름"), + fieldWithPath("data.accessToken").type(STRING).description("엑세스 토큰"), + fieldWithPath("data.refreshToken").type(STRING).description("리프레쉬 토큰")) + .build(); + + RestDocumentationResultHandler document = + documentHandler("social-login-by-access-token", prettyPrint(), parameters); + + // when // then + mockMvc.perform(httpRequest) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(document); + } + @DisplayName("엑세스 토큰 재발급") @Test void reissueAccessToken() throws Exception {