diff --git a/backend/src/main/java/mouda/backend/auth/Infrastructure/AppleOauthClient.java b/backend/src/main/java/mouda/backend/auth/Infrastructure/AppleOauthClient.java index 58011b208..f746cf369 100644 --- a/backend/src/main/java/mouda/backend/auth/Infrastructure/AppleOauthClient.java +++ b/backend/src/main/java/mouda/backend/auth/Infrastructure/AppleOauthClient.java @@ -4,7 +4,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -58,22 +57,23 @@ public String getRefreshToken(String code) { return response.refresh_token(); } - public void revoke(String refreshToken) { - String revokeUrl = APPLE_API_URL + "/oauth2/v2/revoke"; - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("client_id", CLIENT_ID); - formData.add("client_secret", clientSecretProvider.provide()); - formData.add("token", refreshToken); - formData.add("token_hint_type", "refresh_token"); - - ResponseEntity result = restClient.method(HttpMethod.POST) - .uri(revokeUrl) - .headers(httpHeaders -> httpHeaders.addAll(getHttpHeaders())) - .body(formData) - .retrieve() - .toEntity(String.class); - log.info("revoke status code : {}", result.getStatusCode()); - } + // TODO: 애플 심사 시 필요할 수 있으므로 제거하지 않습니다. + // public void revoke(String refreshToken) { + // String revokeUrl = APPLE_API_URL + "/oauth2/v2/revoke"; + // MultiValueMap formData = new LinkedMultiValueMap<>(); + // formData.add("client_id", CLIENT_ID); + // formData.add("client_secret", clientSecretProvider.provide()); + // formData.add("token", refreshToken); + // formData.add("token_hint_type", "refresh_token"); + // + // ResponseEntity result = restClient.method(HttpMethod.POST) + // .uri(revokeUrl) + // .headers(httpHeaders -> httpHeaders.addAll(getHttpHeaders())) + // .body(formData) + // .retrieve() + // .toEntity(String.class); + // log.info("revoke status code : {}", result.getStatusCode()); + // } private MultiValueMap getFormData(String code) { MultiValueMap formData = new LinkedMultiValueMap<>(); diff --git a/backend/src/main/java/mouda/backend/auth/business/AppleAuthService.java b/backend/src/main/java/mouda/backend/auth/business/AppleAuthService.java index 11a8cb9fe..8387ab2a3 100644 --- a/backend/src/main/java/mouda/backend/auth/business/AppleAuthService.java +++ b/backend/src/main/java/mouda/backend/auth/business/AppleAuthService.java @@ -8,11 +8,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import mouda.backend.auth.Infrastructure.AppleOauthClient; import mouda.backend.auth.exception.AuthErrorMessage; import mouda.backend.auth.exception.AuthException; import mouda.backend.auth.implement.AppleOauthManager; -import mouda.backend.auth.implement.LoginManager; import mouda.backend.auth.implement.jwt.AccessTokenProvider; import mouda.backend.auth.presentation.controller.AppleUserInfoRequest; import mouda.backend.member.domain.LoginDetail; @@ -26,13 +24,11 @@ @RequiredArgsConstructor public class AppleAuthService { - private final LoginManager loginManager; private final MemberFinder memberFinder; private final MemberWriter memberWriter; private final AppleOauthManager appleOauthManager; private final AccessTokenProvider accessTokenProvider; private final ObjectMapper objectMapper; - private final AppleOauthClient appleOauthClient; // TODO: 더 이상 사용하지 않는 로직. 로그인 프로세스 정착 후 제거할 것 // public LoginResponse oauthLogin(AppleOauthRequest oauthRequest) { @@ -43,31 +39,36 @@ public class AppleAuthService { // return new LoginResponse(accessToken); // } // LoginProcessResult result = loginManager.processAppleLogin(member); - // return new LoginResponse(result.accessToken());ap + // return new LoginResponse(result.accessToken()); // } - public String getAccessToken(String idToken) { + public String login(String idToken) { String socialLoginId = appleOauthManager.getSocialLoginId(idToken); Member member = memberFinder.findBySocialId(socialLoginId); + reSignup(member); return accessTokenProvider.provide(member); } - public void save(String code, String idToken, String user) { - String refreshToken = appleOauthClient.getRefreshToken(code); + private void reSignup(Member member) { + if (member.isDeleted()) { + member.reSignup(); + } + } + + public void save(String idToken, String user) { try { AppleUserInfoRequest request = objectMapper.readValue(user, AppleUserInfoRequest.class); String firstName = request.name().firstName(); String lastName = request.name().lastName(); - saveMember(refreshToken, idToken, firstName, lastName); + saveMember(idToken, firstName, lastName); } catch (JsonProcessingException exception) { throw new AuthException(HttpStatus.BAD_REQUEST, AuthErrorMessage.APPLE_USER_BAD_REQUEST); } } - private void saveMember(String refreshToken, String idToken, String firstName, String lastName) { + private void saveMember(String idToken, String firstName, String lastName) { String socialLoginId = appleOauthManager.getSocialLoginId(idToken); - log.info("socialLoginId = " + socialLoginId); - Member member = new Member(lastName + firstName, new LoginDetail(OauthType.APPLE, socialLoginId, refreshToken)); + Member member = new Member(lastName + firstName, new LoginDetail(OauthType.APPLE, socialLoginId)); memberWriter.append(member); } } diff --git a/backend/src/main/java/mouda/backend/auth/business/KakaoAuthService.java b/backend/src/main/java/mouda/backend/auth/business/KakaoAuthService.java index f51b49ad4..9835d8281 100644 --- a/backend/src/main/java/mouda/backend/auth/business/KakaoAuthService.java +++ b/backend/src/main/java/mouda/backend/auth/business/KakaoAuthService.java @@ -10,7 +10,6 @@ import mouda.backend.auth.implement.LoginManager; import mouda.backend.auth.implement.jwt.AccessTokenProvider; import mouda.backend.auth.presentation.request.OauthRequest; -import mouda.backend.auth.presentation.response.KakaoLoginResponse; import mouda.backend.auth.presentation.response.LoginResponse; import mouda.backend.member.domain.LoginDetail; import mouda.backend.member.domain.Member; @@ -28,11 +27,11 @@ public class KakaoAuthService { private final MemberWriter memberWriter; private final MemberFinder memberFinder; - public KakaoLoginResponse oauthLogin(OauthRequest oauthRequest) { + public LoginResponse oauthLogin(OauthRequest oauthRequest) { String kakaoId = oauthManager.getSocialLoginId(oauthRequest.code()); LoginProcessResult loginProcessResult = loginManager.processSocialLogin(OauthType.KAKAO, kakaoId, "name"); - return new KakaoLoginResponse(loginProcessResult.memberId(), loginProcessResult.accessToken()); + return new LoginResponse(loginProcessResult.accessToken()); } public LoginResponse basicLoginAnna() { diff --git a/backend/src/main/java/mouda/backend/auth/business/result/LoginProcessResult.java b/backend/src/main/java/mouda/backend/auth/business/result/LoginProcessResult.java index 28be60be3..5aaeaec18 100644 --- a/backend/src/main/java/mouda/backend/auth/business/result/LoginProcessResult.java +++ b/backend/src/main/java/mouda/backend/auth/business/result/LoginProcessResult.java @@ -1,7 +1,6 @@ package mouda.backend.auth.business.result; public record LoginProcessResult( - Long memberId, String accessToken ) { } diff --git a/backend/src/main/java/mouda/backend/auth/implement/LoginManager.java b/backend/src/main/java/mouda/backend/auth/implement/LoginManager.java index 38ce97510..d8bfad66d 100644 --- a/backend/src/main/java/mouda/backend/auth/implement/LoginManager.java +++ b/backend/src/main/java/mouda/backend/auth/implement/LoginManager.java @@ -28,30 +28,23 @@ public class LoginManager { public LoginProcessResult processSocialLogin(OauthType oauthType, String socialLoginId, String name) { Optional member = memberRepository.findByLoginDetail_SocialLoginId(socialLoginId); - - return member.map(value -> { - memberWriter.updateName(value.getId(), name); - return new LoginProcessResult(value.getId(), accessTokenProvider.provide(value)); - }) - .orElseGet(() -> processKakaoLogin(oauthType, socialLoginId, name)); - + if (member.isEmpty()) { + return signup(oauthType, socialLoginId, name); + } + if (member.get().isDeleted()) { + member.get().reSignup(); + memberRepository.save(member.get()); + } + memberWriter.updateName(member.get().getId(), name); + return new LoginProcessResult(accessTokenProvider.provide(member.get())); } - private LoginProcessResult processKakaoLogin(OauthType oauthType, String socialLoginId, String name) { - if (oauthType == OauthType.KAKAO) { + private LoginProcessResult signup(OauthType oauthType, String socialLoginId, String name) { + if (OauthType.KAKAO.equals(oauthType)) { throw new AuthException(HttpStatus.BAD_REQUEST, AuthErrorMessage.KAKAO_CANNOT_SIGNUP); } - Member newMember = Member.builder() - .name(name) - .loginDetail(new LoginDetail(oauthType, socialLoginId)) - .build(); - memberWriter.append(newMember); - - return new LoginProcessResult(newMember.getId(), accessTokenProvider.provide(newMember)); - } - - public LoginProcessResult processAppleLogin(Member member) { - return new LoginProcessResult(member.getId(), accessTokenProvider.provide(member)); + Member member = memberWriter.append(new Member(name, new LoginDetail(oauthType, socialLoginId))); + return new LoginProcessResult(accessTokenProvider.provide(member)); } public String updateOauth(long memberId, OauthType oauthType, String socialLoginId) { diff --git a/backend/src/main/java/mouda/backend/auth/presentation/business/CommonAuthService.java b/backend/src/main/java/mouda/backend/auth/presentation/business/CommonAuthService.java index 6e8c37ff8..c9525e488 100644 --- a/backend/src/main/java/mouda/backend/auth/presentation/business/CommonAuthService.java +++ b/backend/src/main/java/mouda/backend/auth/presentation/business/CommonAuthService.java @@ -5,9 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import mouda.backend.auth.Infrastructure.AppleOauthClient; import mouda.backend.member.domain.Member; -import mouda.backend.member.domain.OauthType; import mouda.backend.member.implement.MemberWriter; @Slf4j @@ -16,14 +14,9 @@ @RequiredArgsConstructor public class CommonAuthService { - private final AppleOauthClient oauthClient; private final MemberWriter memberWriter; public void withdraw(Member member) { - if (OauthType.APPLE.equals(member.getOauthType())) { - log.info("revoke apple user"); - oauthClient.revoke(member.getRefreshToken()); - } - memberWriter.remove(member); + memberWriter.withdraw(member); } } diff --git a/backend/src/main/java/mouda/backend/auth/presentation/controller/AppleAuthController.java b/backend/src/main/java/mouda/backend/auth/presentation/controller/AppleAuthController.java index 841b535a0..0acc57267 100644 --- a/backend/src/main/java/mouda/backend/auth/presentation/controller/AppleAuthController.java +++ b/backend/src/main/java/mouda/backend/auth/presentation/controller/AppleAuthController.java @@ -22,15 +22,14 @@ public class AppleAuthController { @PostMapping("/v1/oauth/apple") public ResponseEntity test( - @RequestParam("code") String code, @RequestParam("id_token") String id_token, @RequestParam(name = "user", required = false) String user ) throws IOException { // TODO: 이전에 가입한 적 있지만 DB를 갈아엎어서 user가 들어오지 않는 경우 save에 실패한다. if (user != null) { - appleAuthService.save(code, id_token, user); + appleAuthService.save(id_token, user); } - String accessToken = appleAuthService.getAccessToken(id_token); + String accessToken = appleAuthService.login(id_token); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add("Location", "https://dev.mouda.site/oauth/apple?token=" + accessToken); return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND); diff --git a/backend/src/main/java/mouda/backend/auth/presentation/controller/AuthController.java b/backend/src/main/java/mouda/backend/auth/presentation/controller/AuthController.java index ff39811b1..1f9aa7aab 100644 --- a/backend/src/main/java/mouda/backend/auth/presentation/controller/AuthController.java +++ b/backend/src/main/java/mouda/backend/auth/presentation/controller/AuthController.java @@ -17,7 +17,6 @@ import mouda.backend.auth.presentation.controller.swagger.AuthSwagger; import mouda.backend.auth.presentation.request.GoogleOauthRequest; import mouda.backend.auth.presentation.request.OauthRequest; -import mouda.backend.auth.presentation.response.KakaoLoginResponse; import mouda.backend.auth.presentation.response.LoginResponse; import mouda.backend.common.config.argumentresolver.LoginMember; import mouda.backend.common.response.RestResponse; @@ -35,8 +34,8 @@ public class AuthController implements AuthSwagger { @Override @PostMapping("/kakao/oauth") - public ResponseEntity> loginKakaoOauth(@RequestBody OauthRequest oauthRequest) { - KakaoLoginResponse response = kakaoAuthService.oauthLogin(oauthRequest); + public ResponseEntity> loginKakaoOauth(@RequestBody OauthRequest oauthRequest) { + LoginResponse response = kakaoAuthService.oauthLogin(oauthRequest); return ResponseEntity.ok().body(new RestResponse<>(response)); } diff --git a/backend/src/main/java/mouda/backend/auth/presentation/controller/swagger/AuthSwagger.java b/backend/src/main/java/mouda/backend/auth/presentation/controller/swagger/AuthSwagger.java index b7e609e60..0922eed2f 100644 --- a/backend/src/main/java/mouda/backend/auth/presentation/controller/swagger/AuthSwagger.java +++ b/backend/src/main/java/mouda/backend/auth/presentation/controller/swagger/AuthSwagger.java @@ -8,7 +8,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import mouda.backend.auth.presentation.request.GoogleOauthRequest; import mouda.backend.auth.presentation.request.OauthRequest; -import mouda.backend.auth.presentation.response.KakaoLoginResponse; import mouda.backend.auth.presentation.response.LoginResponse; import mouda.backend.common.config.argumentresolver.LoginMember; import mouda.backend.common.response.RestResponse; @@ -20,7 +19,7 @@ public interface AuthSwagger { @ApiResponses({ @ApiResponse(responseCode = "200", description = "로그인 성공!"), }) - ResponseEntity> loginKakaoOauth(@RequestBody OauthRequest oauthRequest); + ResponseEntity> loginKakaoOauth(@RequestBody OauthRequest oauthRequest); @Operation(summary = "테스트 용 로그인(안나)", description = "테스트 용 가짜 사용자로 로그인한다(accessToken 발급).") @ApiResponses({ diff --git a/backend/src/main/java/mouda/backend/auth/presentation/response/KakaoLoginResponse.java b/backend/src/main/java/mouda/backend/auth/presentation/response/KakaoLoginResponse.java index fe19832e7..bbae935c2 100644 --- a/backend/src/main/java/mouda/backend/auth/presentation/response/KakaoLoginResponse.java +++ b/backend/src/main/java/mouda/backend/auth/presentation/response/KakaoLoginResponse.java @@ -1,7 +1,6 @@ package mouda.backend.auth.presentation.response; public record KakaoLoginResponse( - Long memberId, String accessToken ) { } diff --git a/backend/src/main/java/mouda/backend/member/domain/LoginDetail.java b/backend/src/main/java/mouda/backend/member/domain/LoginDetail.java index 3b5659376..9cf0a1d05 100644 --- a/backend/src/main/java/mouda/backend/member/domain/LoginDetail.java +++ b/backend/src/main/java/mouda/backend/member/domain/LoginDetail.java @@ -16,8 +16,6 @@ public class LoginDetail { private String socialLoginId; - private String refreshToken; - protected LoginDetail() { } @@ -26,12 +24,6 @@ public LoginDetail(OauthType oauthType, String socialLoginId) { this.socialLoginId = socialLoginId; } - public LoginDetail(OauthType oauthType, String socialLoginId, String refreshToken) { - this.oauthType = oauthType; - this.socialLoginId = socialLoginId; - this.refreshToken = refreshToken; - } - @Override public boolean equals(Object o) { if (this == o) diff --git a/backend/src/main/java/mouda/backend/member/domain/Member.java b/backend/src/main/java/mouda/backend/member/domain/Member.java index feb24bdf1..c35b50fd9 100644 --- a/backend/src/main/java/mouda/backend/member/domain/Member.java +++ b/backend/src/main/java/mouda/backend/member/domain/Member.java @@ -6,6 +6,8 @@ import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -29,11 +31,15 @@ public class Member { @Embedded private LoginDetail loginDetail; + @Enumerated(EnumType.STRING) + private MemberStatus memberStatus; + @Builder public Member(String name, LoginDetail loginDetail) { this.loginDetail = loginDetail; validateName(name); this.name = name; + this.memberStatus = MemberStatus.ACTIVE; } private void validateName(String name) { @@ -50,8 +56,16 @@ public OauthType getOauthType() { return loginDetail.getOauthType(); } - public String getRefreshToken() { - return loginDetail.getRefreshToken(); + public void withdraw() { + this.memberStatus = MemberStatus.DELETED; + } + + public boolean isDeleted() { + return MemberStatus.DELETED.equals(this.memberStatus); + } + + public void reSignup() { + this.memberStatus = MemberStatus.ACTIVE; } @Override diff --git a/backend/src/main/java/mouda/backend/member/domain/MemberStatus.java b/backend/src/main/java/mouda/backend/member/domain/MemberStatus.java new file mode 100644 index 000000000..ebec52b88 --- /dev/null +++ b/backend/src/main/java/mouda/backend/member/domain/MemberStatus.java @@ -0,0 +1,7 @@ +package mouda.backend.member.domain; + +public enum MemberStatus { + + ACTIVE, + DELETED +} diff --git a/backend/src/main/java/mouda/backend/member/implement/MemberWriter.java b/backend/src/main/java/mouda/backend/member/implement/MemberWriter.java index 7854152c3..50a1d08f5 100644 --- a/backend/src/main/java/mouda/backend/member/implement/MemberWriter.java +++ b/backend/src/main/java/mouda/backend/member/implement/MemberWriter.java @@ -25,7 +25,8 @@ public void updateName(long memberId, String name) { memberRepository.updateName(memberId, name); } - public void remove(Member member) { - memberRepository.delete(member); + public void withdraw(Member member) { + member.withdraw(); + memberRepository.save(member); } } diff --git a/backend/src/test/java/mouda/backend/auth/business/GoogleAuthServiceTest.java b/backend/src/test/java/mouda/backend/auth/business/GoogleAuthServiceTest.java new file mode 100644 index 000000000..f3b512494 --- /dev/null +++ b/backend/src/test/java/mouda/backend/auth/business/GoogleAuthServiceTest.java @@ -0,0 +1,55 @@ +package mouda.backend.auth.business; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import mouda.backend.auth.implement.GoogleOauthManager; +import mouda.backend.auth.presentation.request.GoogleOauthRequest; +import mouda.backend.auth.presentation.response.LoginResponse; +import mouda.backend.common.fixture.MemberFixture; +import mouda.backend.member.domain.Member; +import mouda.backend.member.domain.MemberStatus; +import mouda.backend.member.infrastructure.MemberRepository; + +@SpringBootTest +class GoogleAuthServiceTest { + + @Autowired + private GoogleAuthService googleAuthService; + + @Autowired + private MemberRepository memberRepository; + + @MockBean + private GoogleOauthManager googleOauthManager; + + @DisplayName("회원 탈퇴한 사용자가 재가입하는 경우 상태 정보를 변경한다.") + @Test + void processSocialLoginWhoDeletedBefore() { + // given + Member anna = MemberFixture.getAnna(); + anna.withdraw(); + memberRepository.save(anna); + + when(googleOauthManager.getMemberName(anyString())).thenReturn("anna"); + when(googleOauthManager.getSocialLoginId(anyString())).thenReturn(anna.getSocialLoginId()); + + // when + LoginResponse loginResponse = googleAuthService.oauthLogin(new GoogleOauthRequest(null, "IdToken")); + + // then + assertThat(loginResponse.accessToken()).isNotNull(); + Optional member = memberRepository.findByLoginDetail_SocialLoginId("1234"); + assertThat(member.isPresent()).isTrue(); + assertThat(member.get().getMemberStatus()).isEqualTo(MemberStatus.ACTIVE); + } +} diff --git a/backend/src/test/java/mouda/backend/auth/implement/LoginManagerTest.java b/backend/src/test/java/mouda/backend/auth/implement/LoginManagerTest.java index 098bee0f0..53f6fd825 100644 --- a/backend/src/test/java/mouda/backend/auth/implement/LoginManagerTest.java +++ b/backend/src/test/java/mouda/backend/auth/implement/LoginManagerTest.java @@ -39,7 +39,6 @@ void processSocialLogin() { // then assertThat(loginProcessResult.accessToken()).isNotNull(); - assertThat(loginProcessResult.memberId()).isEqualTo(member.getId()); Optional foundMember = memberRepository.findByLoginDetail_SocialLoginId(member.getSocialLoginId()); assertThat(foundMember.isPresent()).isTrue(); assertThat(foundMember.get()).isEqualTo(member); diff --git a/backend/src/test/java/mouda/backend/auth/implement/MemberWriterTest.java b/backend/src/test/java/mouda/backend/auth/implement/MemberWriterTest.java index ebe538356..99c663e85 100644 --- a/backend/src/test/java/mouda/backend/auth/implement/MemberWriterTest.java +++ b/backend/src/test/java/mouda/backend/auth/implement/MemberWriterTest.java @@ -2,6 +2,8 @@ import static org.assertj.core.api.Assertions.*; +import java.util.List; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -36,17 +38,19 @@ void append() { assertThat(memberRepository.findAll()).hasSize(1); } - @DisplayName("멤버를 삭제한다.") + @DisplayName("회원을 삭제 시 상태가 변경된다.") @Test - void remove() { + void withdraw() { // given Member tebah = MemberFixture.getTebah(); memberWriter.append(tebah); // when - memberWriter.remove(tebah); + memberWriter.withdraw(tebah); // then - assertThat(memberRepository.findAll()).hasSize(0); + List members = memberRepository.findAll(); + assertThat(members).hasSize(1); + assertThat(members.get(0)).isEqualTo(tebah); } }