From 1e8106044318ecaabacf96190d74b1cc56ffe813 Mon Sep 17 00:00:00 2001 From: 70825 Date: Fri, 28 Jul 2023 12:10:57 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B0=80=EC=9E=85=EB=90=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EB=9D=BC=EB=A9=B4=20201=20OK=20=EB=B0=98=ED=99=98,=20?= =?UTF-8?q?=EA=B0=80=EC=9E=85=EB=90=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=EB=9D=BC=EB=A9=B4=20302=20FOUND=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/auth/application/AuthService.java | 12 ++-- .../java/com/funeat/auth/dto/SignUserDto.java | 26 ++++++++ .../java/com/funeat/auth/dto/UserInfoDto.java | 6 ++ .../auth/presentation/AuthController.java | 27 ++++++-- .../auth/util/KakaoPlatformUserProvider.java | 15 ++++- .../auth/util/PlatformUserProvider.java | 2 + .../member/application/MemberService.java | 15 ++--- .../acceptance/auth/AuthAcceptanceTest.java | 40 ++++++++++++ .../common/TestPlatformUserProvider.java | 5 ++ .../auth/application/AuthServiceTest.java | 41 ++++++++++++ .../auth/presentation/AuthControllerTest.java | 65 +++++++++++++++++++ .../member/application/MemberServiceTest.java | 12 +++- 12 files changed, 245 insertions(+), 21 deletions(-) create mode 100644 backend/src/main/java/com/funeat/auth/dto/SignUserDto.java create mode 100644 backend/src/test/java/com/funeat/acceptance/auth/AuthAcceptanceTest.java create mode 100644 backend/src/test/java/com/funeat/auth/application/AuthServiceTest.java create mode 100644 backend/src/test/java/com/funeat/auth/presentation/AuthControllerTest.java diff --git a/backend/src/main/java/com/funeat/auth/application/AuthService.java b/backend/src/main/java/com/funeat/auth/application/AuthService.java index c95e48998..0918aa601 100644 --- a/backend/src/main/java/com/funeat/auth/application/AuthService.java +++ b/backend/src/main/java/com/funeat/auth/application/AuthService.java @@ -1,9 +1,9 @@ package com.funeat.auth.application; +import com.funeat.auth.dto.SignUserDto; import com.funeat.auth.dto.UserInfoDto; import com.funeat.auth.util.PlatformUserProvider; import com.funeat.member.application.MemberService; -import com.funeat.member.domain.Member; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,9 +19,13 @@ public AuthService(final MemberService memberService, final PlatformUserProvider this.platformUserProvider = platformUserProvider; } - public Long loginWithKakao(final String code) { + public SignUserDto loginWithKakao(final String code) { final UserInfoDto userInfoDto = platformUserProvider.getPlatformUser(code); - final Member member = memberService.findOrCreateMember(userInfoDto); - return member.getId(); + final SignUserDto signUserDto = memberService.findOrCreateMember(userInfoDto); + return signUserDto; + } + + public String getLoginRedirectUri() { + return platformUserProvider.getRedirectURI(); } } diff --git a/backend/src/main/java/com/funeat/auth/dto/SignUserDto.java b/backend/src/main/java/com/funeat/auth/dto/SignUserDto.java new file mode 100644 index 000000000..170ef9591 --- /dev/null +++ b/backend/src/main/java/com/funeat/auth/dto/SignUserDto.java @@ -0,0 +1,26 @@ +package com.funeat.auth.dto; + +import com.funeat.member.domain.Member; + +public class SignUserDto { + + private final boolean isSignIn; + private final Member member; + + public SignUserDto(final boolean isSignIn, final Member member) { + this.isSignIn = isSignIn; + this.member = member; + } + + public static SignUserDto of(final boolean isSignIn, final Member member) { + return new SignUserDto(isSignIn, member); + } + + public boolean isSignIn() { + return isSignIn; + } + + public Member getMember() { + return member; + } +} diff --git a/backend/src/main/java/com/funeat/auth/dto/UserInfoDto.java b/backend/src/main/java/com/funeat/auth/dto/UserInfoDto.java index a04a04298..3861eac64 100644 --- a/backend/src/main/java/com/funeat/auth/dto/UserInfoDto.java +++ b/backend/src/main/java/com/funeat/auth/dto/UserInfoDto.java @@ -1,5 +1,7 @@ package com.funeat.auth.dto; +import com.funeat.member.domain.Member; + public class UserInfoDto { private final Long id; @@ -20,6 +22,10 @@ public static UserInfoDto from(final KakaoUserInfoDto kakaoUserInfoDto) { ); } + public Member toMember() { + return new Member(this.nickname, this.profileImageUrl, this.id.toString()); + } + public Long getId() { return id; } diff --git a/backend/src/main/java/com/funeat/auth/presentation/AuthController.java b/backend/src/main/java/com/funeat/auth/presentation/AuthController.java index c5713ca7a..6c6093f4f 100644 --- a/backend/src/main/java/com/funeat/auth/presentation/AuthController.java +++ b/backend/src/main/java/com/funeat/auth/presentation/AuthController.java @@ -1,7 +1,10 @@ package com.funeat.auth.presentation; import com.funeat.auth.application.AuthService; +import com.funeat.auth.dto.SignUserDto; +import java.net.URI; import javax.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -16,11 +19,27 @@ public AuthController(final AuthService authService) { this.authService = authService; } + @GetMapping("/api/auth/kakao") + public ResponseEntity kakaoLogin() { + return ResponseEntity.status(HttpStatus.FOUND) + .location(URI.create(authService.getLoginRedirectUri())) + .build(); + } + @GetMapping("/login/oauth2/code/kakao") - public ResponseEntity kakaoLogin(@RequestParam("code") final String code, - final HttpServletRequest request) { - final Long memberId = authService.loginWithKakao(code); + public ResponseEntity loginAuthorizeUser(@RequestParam("code") final String code, + final HttpServletRequest request) { + final SignUserDto signUserDto = authService.loginWithKakao(code); + final Long memberId = signUserDto.getMember().getId(); request.getSession().setAttribute("member", memberId); - return ResponseEntity.ok().build(); + + if (signUserDto.isSignIn()) { + return ResponseEntity.status(HttpStatus.FOUND) + .location(URI.create("/")) + .build(); + } + return ResponseEntity.status(HttpStatus.FOUND) + .location(URI.create("/profile")) + .build(); } } diff --git a/backend/src/main/java/com/funeat/auth/util/KakaoPlatformUserProvider.java b/backend/src/main/java/com/funeat/auth/util/KakaoPlatformUserProvider.java index 511b3372f..3ac1c3b80 100644 --- a/backend/src/main/java/com/funeat/auth/util/KakaoPlatformUserProvider.java +++ b/backend/src/main/java/com/funeat/auth/util/KakaoPlatformUserProvider.java @@ -5,6 +5,7 @@ import com.funeat.auth.dto.KakaoTokenDto; import com.funeat.auth.dto.KakaoUserInfoDto; import com.funeat.auth.dto.UserInfoDto; +import java.util.StringJoiner; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Profile; @@ -98,9 +99,7 @@ private ResponseEntity requestKakaoUserInfo(final String accessToken) { headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); final HttpEntity> request = new HttpEntity<>(null, headers); - ResponseEntity response = restTemplate.postForEntity(RESOURCE_BASE_URL + USER_INFO_URI, request, - String.class); - return response; + return restTemplate.postForEntity(RESOURCE_BASE_URL + USER_INFO_URI, request, String.class); } private KakaoUserInfoDto convertJsonToKakaoUserDto(final String responseBody) { @@ -110,4 +109,14 @@ private KakaoUserInfoDto convertJsonToKakaoUserDto(final String responseBody) { throw new IllegalArgumentException(); } } + + @Override + public String getRedirectURI() { + final StringJoiner joiner = new StringJoiner("&"); + joiner.add("response_type=code"); + joiner.add("client_id=" + kakaoRestApiKey); + joiner.add("redirect_uri=" + redirectUri); + + return AUTHORIZATION_BASE_URL + "?" + joiner; + } } diff --git a/backend/src/main/java/com/funeat/auth/util/PlatformUserProvider.java b/backend/src/main/java/com/funeat/auth/util/PlatformUserProvider.java index a8d67923e..2d77b4411 100644 --- a/backend/src/main/java/com/funeat/auth/util/PlatformUserProvider.java +++ b/backend/src/main/java/com/funeat/auth/util/PlatformUserProvider.java @@ -5,4 +5,6 @@ public interface PlatformUserProvider { UserInfoDto getPlatformUser(final String code); + + String getRedirectURI(); } diff --git a/backend/src/main/java/com/funeat/member/application/MemberService.java b/backend/src/main/java/com/funeat/member/application/MemberService.java index ca1895b81..bae8ba907 100644 --- a/backend/src/main/java/com/funeat/member/application/MemberService.java +++ b/backend/src/main/java/com/funeat/member/application/MemberService.java @@ -1,5 +1,6 @@ package com.funeat.member.application; +import com.funeat.auth.dto.SignUserDto; import com.funeat.auth.dto.UserInfoDto; import com.funeat.member.domain.Member; import com.funeat.member.persistence.MemberRepository; @@ -16,21 +17,19 @@ public MemberService(final MemberRepository memberRepository) { this.memberRepository = memberRepository; } - public Member findOrCreateMember(final UserInfoDto userInfoDto) { + public SignUserDto findOrCreateMember(final UserInfoDto userInfoDto) { final String platformId = userInfoDto.getId().toString(); return memberRepository.findByPlatformId(platformId) + .map(value -> SignUserDto.of(false, value)) .orElseGet(() -> save(userInfoDto)); } @Transactional - public Member save(final UserInfoDto userInfoDto) { - final String nickname = userInfoDto.getNickname(); - final String profileImage = userInfoDto.getProfileImageUrl(); - final String platformId = userInfoDto.getId().toString(); - - final Member member = new Member(nickname, profileImage, platformId); + public SignUserDto save(final UserInfoDto userInfoDto) { + final Member member = userInfoDto.toMember(); + memberRepository.save(member); - return memberRepository.save(member); + return SignUserDto.of(true, member); } } diff --git a/backend/src/test/java/com/funeat/acceptance/auth/AuthAcceptanceTest.java b/backend/src/test/java/com/funeat/acceptance/auth/AuthAcceptanceTest.java new file mode 100644 index 000000000..32667362d --- /dev/null +++ b/backend/src/test/java/com/funeat/acceptance/auth/AuthAcceptanceTest.java @@ -0,0 +1,40 @@ +package com.funeat.acceptance.auth; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import com.funeat.acceptance.common.AcceptanceTest; +import com.funeat.auth.application.AuthService; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@SuppressWarnings("NonAsciiCharacters") +public class AuthAcceptanceTest extends AcceptanceTest { + + @Autowired + private AuthService authService; + + @Test + void 유저가_카카오_로그인_버튼을_누르면_카카오_로그인_페이지로_리다이렉트할_수_있다() { + // given + final var response = 카카오_로그인_버튼_클릭(); + final var expected = authService.getLoginRedirectUri(); + + // when + final String actual = response.header("Location"); + + // then + assertThat(actual).isEqualTo(expected); + } + + private ExtractableResponse 카카오_로그인_버튼_클릭() { + return given() + .redirects().follow(false) + .when() + .get("/api/auth/kakao") + .then() + .extract(); + } +} diff --git a/backend/src/test/java/com/funeat/acceptance/common/TestPlatformUserProvider.java b/backend/src/test/java/com/funeat/acceptance/common/TestPlatformUserProvider.java index f0eb6821b..20b041e2b 100644 --- a/backend/src/test/java/com/funeat/acceptance/common/TestPlatformUserProvider.java +++ b/backend/src/test/java/com/funeat/acceptance/common/TestPlatformUserProvider.java @@ -13,4 +13,9 @@ public class TestPlatformUserProvider implements PlatformUserProvider { public UserInfoDto getPlatformUser(final String code) { return new UserInfoDto(1L, "test", "https://www.test.com"); } + + @Override + public String getRedirectURI() { + return "https://www.test.com"; + } } diff --git a/backend/src/test/java/com/funeat/auth/application/AuthServiceTest.java b/backend/src/test/java/com/funeat/auth/application/AuthServiceTest.java new file mode 100644 index 000000000..3e74d9bf4 --- /dev/null +++ b/backend/src/test/java/com/funeat/auth/application/AuthServiceTest.java @@ -0,0 +1,41 @@ +package com.funeat.auth.application; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.funeat.auth.dto.SignUserDto; +import com.funeat.common.DataClearExtension; +import com.funeat.member.domain.Member; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@SpringBootTest +@ExtendWith(DataClearExtension.class) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(ReplaceUnderscores.class) +public class AuthServiceTest { + + @Autowired + private AuthService authService; + + @Test + void 카카오_로그인을_하여_유저_정보를_가져온다() { + // given + final var code = "test"; + final var member = new Member("test", "https://www.test.com", "1"); + final var expected = SignUserDto.of(true, member); + + // when + final var actual = authService.loginWithKakao(code); + + // then + assertThat(actual).usingRecursiveComparison() + .ignoringFields("member.id") + .isEqualTo(expected); + } +} diff --git a/backend/src/test/java/com/funeat/auth/presentation/AuthControllerTest.java b/backend/src/test/java/com/funeat/auth/presentation/AuthControllerTest.java new file mode 100644 index 000000000..cac2b8879 --- /dev/null +++ b/backend/src/test/java/com/funeat/auth/presentation/AuthControllerTest.java @@ -0,0 +1,65 @@ +package com.funeat.auth.presentation; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.funeat.auth.application.AuthService; +import com.funeat.auth.dto.SignUserDto; +import com.funeat.member.domain.Member; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(AuthController.class) +@SuppressWarnings("NonAsciiCharacters") +public class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private AuthService authService; + + @Nested + class loginAuthorizeUser_테스트 { + + @Test + void 이미_가입된_유저라면_홈_경로로_리다이렉트한다() throws Exception { + // given + final var code = "test"; + final var member = new Member("test", "www.test.com", "1"); + final var signUserDto = SignUserDto.of(true, member); + + // when + when(authService.loginWithKakao(code)).thenReturn(signUserDto); + + // then + mockMvc.perform(get("/login/oauth2/code/kakao") + .param("code", code)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")); + } + + @Test + void 가입되지_않은_유저라면_프로필_경로로_리다이렉트한다() throws Exception { + // given + final var code = "test"; + final var member = new Member("test", "www.test.com", "1"); + final var signUserDto = SignUserDto.of(false, member); + + // when + when(authService.loginWithKakao(code)).thenReturn(signUserDto); + + // then + mockMvc.perform(get("/login/oauth2/code/kakao") + .param("code", code)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/profile")); + } + } +} diff --git a/backend/src/test/java/com/funeat/member/application/MemberServiceTest.java b/backend/src/test/java/com/funeat/member/application/MemberServiceTest.java index 31366ef16..65967f853 100644 --- a/backend/src/test/java/com/funeat/member/application/MemberServiceTest.java +++ b/backend/src/test/java/com/funeat/member/application/MemberServiceTest.java @@ -6,6 +6,8 @@ import com.funeat.common.DataClearExtension; import com.funeat.member.domain.Member; import com.funeat.member.persistence.MemberRepository; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Nested; @@ -44,7 +46,10 @@ class findOrCreateMember_테스트 { final var actual = memberService.findOrCreateMember(userInfoDto); // then - assertThat(expected).containsExactly(actual); + SoftAssertions.assertSoftly(softAssertions -> { + Assertions.assertFalse(actual.isSignIn()); + assertThat(expected).containsExactly(actual.getMember()); + }); } @Test @@ -60,7 +65,10 @@ class findOrCreateMember_테스트 { final var actual = memberService.findOrCreateMember(userInfoDto); // then - assertThat(expected).doesNotContain(actual); + SoftAssertions.assertSoftly(softAssertions -> { + Assertions.assertTrue(actual.isSignIn()); + assertThat(expected).doesNotContain(actual.getMember()); + }); } } }