From f370cdc0a1c739c62d1860c20f3d02ed87f9952d Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 11 Oct 2023 18:41:19 +0900 Subject: [PATCH 1/7] =?UTF-8?q?chore:=20flyway=20=EB=B2=84=EC=A0=84=2017?= =?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 --- .../db/migration/V17__alter_user_oauth_type_tables.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backend/ddang/src/main/resources/db/migration/V17__alter_user_oauth_type_tables.sql diff --git a/backend/ddang/src/main/resources/db/migration/V17__alter_user_oauth_type_tables.sql b/backend/ddang/src/main/resources/db/migration/V17__alter_user_oauth_type_tables.sql new file mode 100644 index 000000000..1a614bb22 --- /dev/null +++ b/backend/ddang/src/main/resources/db/migration/V17__alter_user_oauth_type_tables.sql @@ -0,0 +1,3 @@ +ALTER TABLE users ADD oauth2_type VARCHAR(10); +UPDATE users SET oauth2_type = 'KAKAO' WHERE oauth2_type is null; +ALTER TABLE users MODIFY oauth2_type VARCHAR(10) NOT NULL; From 664d5b0fa45194290504dc13446163d7d9c6b0b8 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 11 Oct 2023 18:42:22 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20User=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=ED=95=9C=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=A9=EC=8B=9D=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ddang/ddang/user/domain/User.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java index b6bcf6278..301d4cf90 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java @@ -1,5 +1,6 @@ package com.ddang.ddang.user.domain; +import com.ddang.ddang.authentication.infrastructure.oauth2.Oauth2Type; import com.ddang.ddang.common.entity.BaseTimeEntity; import com.ddang.ddang.image.domain.ProfileImage; import com.ddang.ddang.review.domain.Review; @@ -8,6 +9,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; import jakarta.persistence.GeneratedValue; @@ -29,12 +32,12 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @EqualsAndHashCode(of = "id", callSuper = false) -@ToString(of = {"id", "name", "reliability", "oauthId", "deleted"}) +@ToString(of = {"id", "name", "reliability", "oauthId", "deleted", "oauth2Type"}) @Table(name = "users") public class User extends BaseTimeEntity { private static final boolean DELETED_STATUS = true; - private static final String UNKOWN_NAME = "알 수 없음"; + private static final String UNKNOWN_NAME = "알 수 없음"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -56,17 +59,22 @@ public class User extends BaseTimeEntity { @Column(name = "is_deleted") private boolean deleted = false; + @Enumerated(EnumType.STRING) + private Oauth2Type oauth2Type; + @Builder private User( final String name, final ProfileImage profileImage, final Reliability reliability, - final String oauthId + final String oauthId, + final Oauth2Type oauth2Type ) { this.name = name; this.profileImage = profileImage; this.reliability = processReliability(reliability); this.oauthId = oauthId; + this.oauth2Type = oauth2Type; } private Reliability processReliability(final Reliability reliability) { From f352cc16e5fc0da07956100250d6b9d8ed735570 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 11 Oct 2023 18:42:45 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=8B=9C=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20DB=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthenticationService.java | 14 ++++++-------- .../presentation/AuthenticationController.java | 5 ++--- .../application/AuthenticationServiceTest.java | 13 +++++++------ .../fixture/AuthenticationServiceFixture.java | 7 ++++++- .../presentation/AuthenticationControllerTest.java | 12 ++++-------- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java index 58add0567..e947a008f 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java @@ -1,5 +1,7 @@ package com.ddang.ddang.authentication.application; +import static com.ddang.ddang.image.domain.ProfileImage.DEFAULT_PROFILE_IMAGE_STORE_NAME; + import com.ddang.ddang.authentication.application.dto.TokenDto; import com.ddang.ddang.authentication.application.exception.InvalidWithdrawalException; import com.ddang.ddang.authentication.application.util.RandomNameGenerator; @@ -21,15 +23,12 @@ import com.ddang.ddang.user.domain.Reliability; import com.ddang.ddang.user.domain.User; import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository; +import java.time.LocalDateTime; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Map; - -import static com.ddang.ddang.image.domain.ProfileImage.DEFAULT_PROFILE_IMAGE_STORE_NAME; - @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -131,17 +130,16 @@ public boolean validateToken(final String accessToken) { @Transactional public void withdrawal( - final Oauth2Type oauth2Type, final String accessToken, final String refreshToken ) throws InvalidWithdrawalException { - final OAuth2UserInformationProvider provider = providerComposite.findProvider(oauth2Type); final PrivateClaims privateClaims = tokenDecoder.decode(TokenType.ACCESS, accessToken) .orElseThrow(() -> new InvalidTokenException("유효한 토큰이 아닙니다.") ); final User user = userRepository.findByIdAndDeletedIsFalse(privateClaims.userId()) - .orElseThrow(() -> new InvalidWithdrawalException("탈퇴에 대한 권한 없습니다.")); + .orElseThrow(() -> new InvalidWithdrawalException("탈퇴에 대한 권한이 없습니다.")); + final OAuth2UserInformationProvider provider = providerComposite.findProvider(user.getOauth2Type()); user.withdrawal(); blackListTokenService.registerBlackListToken(accessToken, refreshToken); diff --git a/backend/ddang/src/main/java/com/ddang/ddang/authentication/presentation/AuthenticationController.java b/backend/ddang/src/main/java/com/ddang/ddang/authentication/presentation/AuthenticationController.java index 0e2b0eed7..90194f91d 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/authentication/presentation/AuthenticationController.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/authentication/presentation/AuthenticationController.java @@ -67,13 +67,12 @@ public ResponseEntity logout( .build(); } - @PostMapping("/withdrawal/{oauth2Type}") + @PostMapping("/withdrawal") public ResponseEntity withdrawal( - @PathVariable final Oauth2Type oauth2Type, @RequestHeader(HttpHeaders.AUTHORIZATION) final String accessToken, @RequestBody @Valid final WithdrawalRequest request ) { - authenticationService.withdrawal(oauth2Type, accessToken, request.refreshToken()); + authenticationService.withdrawal(accessToken, request.refreshToken()); return ResponseEntity.noContent() .build(); diff --git a/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java index 6238cad68..afe21c90e 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java @@ -224,11 +224,12 @@ void setUp() { @Test void 가입한_회원이_탈퇴하는_경우_정상처리한다() throws InvalidWithdrawalException { // given + //given(tokenDecoder.decode(TokenType.ACCESS, anyString())).willReturn(Optional.of(사용자_id_클레임)); given(providerComposite.findProvider(지원하는_소셜_로그인_타입)).willReturn(userInfoProvider); given(userInfoProvider.findUserInformation(anyString())).willReturn(사용자_회원_정보); // when - authenticationService.withdrawal(지원하는_소셜_로그인_타입, 유효한_액세스_토큰, 유효한_리프레시_토큰); + authenticationService.withdrawal(유효한_액세스_토큰, 유효한_리프레시_토큰); // then assertThat(사용자.isDeleted()).isTrue(); @@ -241,9 +242,9 @@ void setUp() { given(userInfoProvider.unlinkUserBy(anyString())).willReturn(탈퇴한_사용자_회원_정보); // when && then - assertThatThrownBy(() -> authenticationService.withdrawal(지원하는_소셜_로그인_타입, 탈퇴한_사용자_액세스_토큰, 유효한_리프레시_토큰)) + assertThatThrownBy(() -> authenticationService.withdrawal(탈퇴한_사용자_액세스_토큰, 유효한_리프레시_토큰)) .isInstanceOf(InvalidWithdrawalException.class) - .hasMessage("탈퇴에 대한 권한 없습니다."); + .hasMessage("탈퇴에 대한 권한이 없습니다."); } @Test @@ -253,15 +254,15 @@ void setUp() { given(userInfoProvider.findUserInformation(anyString())).willThrow(new InvalidTokenException("401 Unauthorized")); // when & then - assertThatThrownBy(() -> authenticationService.withdrawal(지원하는_소셜_로그인_타입, 존재하지_않는_사용자_액세스_토큰, 유효한_리프레시_토큰)) + assertThatThrownBy(() -> authenticationService.withdrawal(존재하지_않는_사용자_액세스_토큰, 유효한_리프레시_토큰)) .isInstanceOf(InvalidWithdrawalException.class) - .hasMessage("탈퇴에 대한 권한 없습니다."); + .hasMessage("탈퇴에 대한 권한이 없습니다."); } @Test void 탈퇴할_때_유효한_토큰이_아닌_경우_예외가_발생한다() { // when & then - assertThatThrownBy(() -> authenticationService.withdrawal(지원하는_소셜_로그인_타입, 유효하지_않은_액세스_토큰, 유효한_리프레시_토큰)) + assertThatThrownBy(() -> authenticationService.withdrawal(유효하지_않은_액세스_토큰, 유효한_리프레시_토큰)) .isInstanceOf(InvalidTokenException.class) .hasMessage("유효한 토큰이 아닙니다."); } diff --git a/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/fixture/AuthenticationServiceFixture.java b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/fixture/AuthenticationServiceFixture.java index 19ef0f791..7b1b827dd 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/fixture/AuthenticationServiceFixture.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/fixture/AuthenticationServiceFixture.java @@ -3,6 +3,7 @@ import com.ddang.ddang.authentication.domain.TokenEncoder; import com.ddang.ddang.authentication.domain.TokenType; import com.ddang.ddang.authentication.domain.dto.UserInformationDto; +import com.ddang.ddang.authentication.infrastructure.jwt.PrivateClaims; import com.ddang.ddang.authentication.infrastructure.oauth2.Oauth2Type; import com.ddang.ddang.device.domain.DeviceToken; import com.ddang.ddang.device.infrastructure.persistence.JpaDeviceTokenRepository; @@ -33,6 +34,8 @@ public class AuthenticationServiceFixture { protected User 사용자; protected User 탈퇴한_사용자; + protected PrivateClaims 사용자_id_클레임 = new PrivateClaims(1L); + protected UserInformationDto 사용자_회원_정보 = new UserInformationDto(12345L); protected UserInformationDto 탈퇴한_사용자_회원_정보 = new UserInformationDto(54321L); protected UserInformationDto 가입하지_않은_사용자_회원_정보 = new UserInformationDto(-99999L); @@ -67,6 +70,7 @@ void fixtureSetUp() { .profileImage(new ProfileImage("upload.png", "store.png")) .reliability(new Reliability(0.0d)) .oauthId("12345") + .oauth2Type(Oauth2Type.KAKAO) .build(); 탈퇴한_사용자 = User.builder() @@ -74,6 +78,7 @@ void fixtureSetUp() { .profileImage(new ProfileImage("upload.png", "store.png")) .reliability(new Reliability(0.0d)) .oauthId("12346") + .oauth2Type(Oauth2Type.KAKAO) .build(); userRepository.save(사용자); @@ -90,7 +95,7 @@ void fixtureSetUp() { ); 만료된_리프레시_토큰 = tokenEncoder.encode( - LocalDateTime.ofInstant(Instant.parse("2023-01-01T22:21:20Z"), ZoneId.of("UTC")), + LocalDateTime.ofInstant(Instant.parse("2023-01-01T22:21:20Z"), ZoneId.of("UTC")), TokenType.REFRESH, Map.of("userId", 1L) ); diff --git a/backend/ddang/src/test/java/com/ddang/ddang/authentication/presentation/AuthenticationControllerTest.java b/backend/ddang/src/test/java/com/ddang/ddang/authentication/presentation/AuthenticationControllerTest.java index 13a707c9f..b8d7d7eb4 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/authentication/presentation/AuthenticationControllerTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/authentication/presentation/AuthenticationControllerTest.java @@ -1,6 +1,5 @@ package com.ddang.ddang.authentication.presentation; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -201,11 +200,11 @@ void setUp() { @Test void ouath2Type과_accessToken과_refreshToken을_전달하면_탈퇴한다() throws Exception { // given - willDoNothing().given(authenticationService).withdrawal(any(), anyString(), anyString()); + willDoNothing().given(authenticationService).withdrawal(anyString(), anyString()); // when & then final ResultActions resultActions = - mockMvc.perform(RestDocumentationRequestBuilders.post("/oauth2/withdrawal/{oauth2Type}", 소셜_로그인_타입) + mockMvc.perform(RestDocumentationRequestBuilders.post("/oauth2/withdrawal") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(유효한_회원탈퇴_요청)) .header(HttpHeaders.AUTHORIZATION, 유효한_액세스_토큰_내용) @@ -221,10 +220,10 @@ void setUp() { void ouath2Type과_accessToken과_refreshToken을_전달시_이미_탈퇴_혹은_존재하지_않아_권한이_없는_회원인_경우_403을_반환한다() throws Exception { // given willThrow(new InvalidWithdrawalException("탈퇴에 대한 권한 없습니다.")).given(authenticationService) - .withdrawal(any(), anyString(), anyString()); + .withdrawal(anyString(), anyString()); // when & then - mockMvc.perform(RestDocumentationRequestBuilders.post("/oauth2/withdrawal/{oauth2Type}", 소셜_로그인_타입) + mockMvc.perform(RestDocumentationRequestBuilders.post("/oauth2/withdrawal") .header(HttpHeaders.AUTHORIZATION, 유효한_액세스_토큰_내용) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(유효하지_않은_회원탈퇴_요청)) @@ -301,9 +300,6 @@ void setUp() { private void withdrawal_문서화(final ResultActions resultActions) throws Exception { resultActions.andDo( restDocs.document( - pathParameters( - parameterWithName("oauth2Type").description("소셜 로그인을 할 서비스 선택(kakao로 고정)") - ), requestHeaders( headerWithName("Authorization").description("회원 Bearer 인증 정보") ), From 394869bdabf61a40df03be8d7d1085c5041c6b38 Mon Sep 17 00:00:00 2001 From: apptie Date: Thu, 12 Oct 2023 12:49:32 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20Oauth2=EC=99=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=EB=90=9C=20=ED=95=84=EB=93=9C=EB=A5=BC=20=EB=B3=84?= =?UTF-8?q?=EB=8F=84=EC=9D=98=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthenticationService.java | 7 +++-- .../qna/application/dto/ReadUserInQnaDto.java | 2 +- .../application/dto/ReadUserInReportDto.java | 2 +- .../application/dto/ReadUserInReviewDto.java | 2 +- .../user/application/dto/ReadUserDto.java | 2 +- .../ddang/user/domain/OauthInformation.java | 31 +++++++++++++++++++ .../com/ddang/ddang/user/domain/User.java | 11 ++----- .../persistence/JpaUserRepository.java | 8 ++++- .../persistence/JpaUserRepositoryTest.java | 11 +++---- 9 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 backend/ddang/src/main/java/com/ddang/ddang/user/domain/OauthInformation.java diff --git a/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java index e947a008f..7fc9bd1b2 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java @@ -62,7 +62,7 @@ private void updateOrPersistDeviceToken(final String deviceToken, final User per } private User findOrPersistUser(final Oauth2Type oauth2Type, final UserInformationDto userInformationDto) { - return userRepository.findByOauthIdAndDeletedIsFalse(userInformationDto.findUserId()) + return userRepository.findByOauthId(userInformationDto.findUserId()) .orElseGet(() -> { final User user = User.builder() .name(oauth2Type.calculateNickname(calculateRandomNumber())) @@ -139,11 +139,12 @@ public void withdrawal( ); final User user = userRepository.findByIdAndDeletedIsFalse(privateClaims.userId()) .orElseThrow(() -> new InvalidWithdrawalException("탈퇴에 대한 권한이 없습니다.")); - final OAuth2UserInformationProvider provider = providerComposite.findProvider(user.getOauth2Type()); + final OAuth2UserInformationProvider provider = providerComposite.findProvider(user.getOauthInformation() + .getOauth2Type()); user.withdrawal(); blackListTokenService.registerBlackListToken(accessToken, refreshToken); deviceTokenRepository.deleteByUserId(user.getId()); - provider.unlinkUserBy(user.getOauthId()); + provider.unlinkUserBy(user.getOauthInformation().getOauthId()); } } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/qna/application/dto/ReadUserInQnaDto.java b/backend/ddang/src/main/java/com/ddang/ddang/qna/application/dto/ReadUserInQnaDto.java index d0a6b2812..a75d44558 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/qna/application/dto/ReadUserInQnaDto.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/qna/application/dto/ReadUserInQnaDto.java @@ -16,7 +16,7 @@ public static ReadUserInQnaDto from(final User writer) { writer.getName(), writer.getProfileImage().getId(), writer.getReliability().getValue(), - writer.getOauthId(), + writer.getOauthInformation().getOauthId(), writer.isDeleted() ); } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/report/application/dto/ReadUserInReportDto.java b/backend/ddang/src/main/java/com/ddang/ddang/report/application/dto/ReadUserInReportDto.java index dbc8db994..cd4fbd220 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/report/application/dto/ReadUserInReportDto.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/report/application/dto/ReadUserInReportDto.java @@ -18,7 +18,7 @@ public static ReadUserInReportDto from(final User user) { user.getName(), ImageIdProcessor.process(user.getProfileImage()), user.getReliability().getValue(), - user.getOauthId(), + user.getOauthInformation().getOauthId(), user.isDeleted() ); } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/review/application/dto/ReadUserInReviewDto.java b/backend/ddang/src/main/java/com/ddang/ddang/review/application/dto/ReadUserInReviewDto.java index ecbf938e8..bb0fa0927 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/review/application/dto/ReadUserInReviewDto.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/review/application/dto/ReadUserInReviewDto.java @@ -10,7 +10,7 @@ public static ReadUserInReviewDto from(final User user) { user.getName(), user.getProfileImage().getId(), user.getReliability().getValue(), - user.getOauthId() + user.getOauthInformation().getOauthId() ); } } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/application/dto/ReadUserDto.java b/backend/ddang/src/main/java/com/ddang/ddang/user/application/dto/ReadUserDto.java index 15ca854b6..015310258 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/application/dto/ReadUserDto.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/application/dto/ReadUserDto.java @@ -18,7 +18,7 @@ public static ReadUserDto from(final User user) { user.getName(), ImageIdProcessor.process(user.getProfileImage()), user.getReliability().getValue(), - user.getOauthId(), + user.getOauthInformation().getOauthId(), user.isDeleted() ); } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/domain/OauthInformation.java b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/OauthInformation.java new file mode 100644 index 000000000..7e069b7fa --- /dev/null +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/OauthInformation.java @@ -0,0 +1,31 @@ +package com.ddang.ddang.user.domain; + +import com.ddang.ddang.authentication.infrastructure.oauth2.Oauth2Type; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@EqualsAndHashCode +@ToString +public class OauthInformation { + + private String oauthId; + + @Column(name = "oauth2_type") + @Enumerated(EnumType.STRING) + private Oauth2Type oauth2Type; + + public OauthInformation(final String oauthId, final Oauth2Type oauth2Type) { + this.oauthId = oauthId; + this.oauth2Type = oauth2Type; + } +} diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java index 301d4cf90..3ae74b8c6 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java @@ -9,8 +9,6 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; import jakarta.persistence.GeneratedValue; @@ -54,14 +52,12 @@ public class User extends BaseTimeEntity { @AttributeOverride(name = "value", column = @Column(name = "reliability")) private Reliability reliability; - private String oauthId; + @Embedded + private OauthInformation oauthInformation; @Column(name = "is_deleted") private boolean deleted = false; - @Enumerated(EnumType.STRING) - private Oauth2Type oauth2Type; - @Builder private User( final String name, @@ -73,8 +69,7 @@ private User( this.name = name; this.profileImage = profileImage; this.reliability = processReliability(reliability); - this.oauthId = oauthId; - this.oauth2Type = oauth2Type; + this.oauthInformation = new OauthInformation(oauthId, oauth2Type); } private Reliability processReliability(final Reliability reliability) { diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java b/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java index 390768b88..81e78042d 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java @@ -4,10 +4,16 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; +import org.springframework.data.jpa.repository.Query; public interface JpaUserRepository extends JpaRepository { - Optional findByOauthIdAndDeletedIsFalse(final String oauthId); + @Query(""" + SELECT u + FROM User u + WHERE u.deleted = false AND u.oauthInformation.oauthId = :oauthId + """) + Optional findByOauthId(final String oauthId); Optional findByIdAndDeletedIsFalse(final Long id); diff --git a/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java b/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java index c6564ec79..c86733036 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java @@ -1,5 +1,7 @@ package com.ddang.ddang.user.infrastructure.persistence; +import static org.assertj.core.api.Assertions.assertThat; + import com.ddang.ddang.configuration.JpaConfiguration; import com.ddang.ddang.configuration.QuerydslConfiguration; import com.ddang.ddang.user.domain.Reliability; @@ -7,6 +9,7 @@ import com.ddang.ddang.user.infrastructure.persistence.fixture.JpaUserRepositoryFixture; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import java.util.Optional; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -14,10 +17,6 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; - @DataJpaTest @Import({JpaConfiguration.class, QuerydslConfiguration.class}) @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @@ -53,7 +52,7 @@ class JpaUserRepositoryTest extends JpaUserRepositoryFixture { @Test void 존재하는_oauthId를_전달하면_해당_회원을_Optional로_감싸_반환한다() { // when - final Optional actual = userRepository.findByOauthIdAndDeletedIsFalse(사용자.getOauthId()); + final Optional actual = userRepository.findByOauthId(사용자.getOauthInformation().getOauthId()); // then assertThat(actual).contains(사용자); @@ -62,7 +61,7 @@ class JpaUserRepositoryTest extends JpaUserRepositoryFixture { @Test void 존재하지_않는_oauthId를_전달하면_해당_회원을_빈_Optional로_반환한다() { // when - final Optional actual = userRepository.findByOauthIdAndDeletedIsFalse(존재하지_않는_oauth_아이디); + final Optional actual = userRepository.findByOauthId(존재하지_않는_oauth_아이디); // then assertThat(actual).isEmpty(); From f8b754f0caa2b9873032d32d0342f3206498cffa Mon Sep 17 00:00:00 2001 From: apptie Date: Thu, 12 Oct 2023 15:44:16 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20flyway=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EB=B2=84=EC=A0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...auth_type_tables.sql => V18__alter_user_oauth_type_tables.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/ddang/src/main/resources/db/migration/{V17__alter_user_oauth_type_tables.sql => V18__alter_user_oauth_type_tables.sql} (100%) diff --git a/backend/ddang/src/main/resources/db/migration/V17__alter_user_oauth_type_tables.sql b/backend/ddang/src/main/resources/db/migration/V18__alter_user_oauth_type_tables.sql similarity index 100% rename from backend/ddang/src/main/resources/db/migration/V17__alter_user_oauth_type_tables.sql rename to backend/ddang/src/main/resources/db/migration/V18__alter_user_oauth_type_tables.sql From c1ef727dc6821bf56ef8b4c164871d46ac607491 Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 13 Oct 2023 09:44:23 +0900 Subject: [PATCH 6/7] =?UTF-8?q?test:=20=EC=9D=98=EB=AF=B8=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20mocking=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/application/AuthenticationServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java index afe21c90e..37154d456 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationServiceTest.java @@ -224,7 +224,6 @@ void setUp() { @Test void 가입한_회원이_탈퇴하는_경우_정상처리한다() throws InvalidWithdrawalException { // given - //given(tokenDecoder.decode(TokenType.ACCESS, anyString())).willReturn(Optional.of(사용자_id_클레임)); given(providerComposite.findProvider(지원하는_소셜_로그인_타입)).willReturn(userInfoProvider); given(userInfoProvider.findUserInformation(anyString())).willReturn(사용자_회원_정보); From 7011751191c582df6c965ac5e4f984093dd7460f Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 13 Oct 2023 17:35:50 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20access=20token=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=8B=9C=20refresh=20token=EB=8F=84=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthenticationService.java | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java index 4f16be5dd..ec0ee41f9 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationService.java @@ -76,18 +76,18 @@ private LoginUserInformationDto findOrPersistUser( final AtomicBoolean isSignUpUser = new AtomicBoolean(false); final User signInUser = userRepository.findByOauthId(userInformationDto.findUserId()) - .orElseGet(() -> { - final User user = User.builder() - .name(oauth2Type.calculateNickname( - calculateRandomNumber())) - .profileImage(findDefaultProfileImage()) - .reliability(new Reliability(0.0d)) - .oauthId(userInformationDto.findUserId()) - .build(); - - isSignUpUser.set(true); - return userRepository.save(user); - }); + .orElseGet(() -> { + final User user = User.builder() + .name(oauth2Type.calculateNickname( + calculateRandomNumber())) + .profileImage(findDefaultProfileImage()) + .reliability(new Reliability(0.0d)) + .oauthId(userInformationDto.findUserId()) + .build(); + + isSignUpUser.set(true); + return userRepository.save(user); + }); return new LoginUserInformationDto(signInUser, isSignUpUser.get()); } @@ -139,7 +139,13 @@ public TokenDto refreshToken(final String refreshToken) { Map.of(PRIVATE_CLAIMS_KEY, privateClaims.userId()) ); - return new TokenDto(accessToken, refreshToken); + final String newRefreshToken = tokenEncoder.encode( + LocalDateTime.now(), + TokenType.REFRESH, + Map.of(PRIVATE_CLAIMS_KEY, privateClaims.userId()) + ); + + return new TokenDto(accessToken, newRefreshToken); } public boolean validateToken(final String accessToken) { @@ -149,17 +155,17 @@ public boolean validateToken(final String accessToken) { @Transactional public void withdrawal( - final Oauth2Type oauth2Type, final String accessToken, final String refreshToken ) throws InvalidWithdrawalException { - final OAuth2UserInformationProvider provider = providerComposite.findProvider(oauth2Type); final PrivateClaims privateClaims = tokenDecoder.decode(TokenType.ACCESS, accessToken) .orElseThrow(() -> new InvalidTokenException("유효한 토큰이 아닙니다.") ); final User user = userRepository.findByIdAndDeletedIsFalse(privateClaims.userId()) - .orElseThrow(() -> new InvalidWithdrawalException("탈퇴에 대한 권한 없습니다.")); + .orElseThrow(() -> new InvalidWithdrawalException("탈퇴에 대한 권한이 없습니다.")); + final OAuth2UserInformationProvider provider = providerComposite.findProvider(user.getOauthInformation() + .getOauth2Type()); user.withdrawal(); blackListTokenService.registerBlackListToken(accessToken, refreshToken);