From 73286011661190fe4d2e66dee931b338b039cb4b Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:33:44 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=ED=8A=B9=EC=A0=95=20=EB=94=94=EB=B0=94=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=ED=86=A0=ED=81=B0=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthenticationService.java | 4 +++- .../application/service/DeviceTokenService.java | 8 +++++++- .../service/DeviceTokenServiceTest.java | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java b/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java index a8abc646..4e6bcf0d 100644 --- a/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java +++ b/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java @@ -88,7 +88,7 @@ private TokenDto convertToTokenDto(final User user) { private void saveOrActiveToken(final User user, final String deviceToken) { if (deviceToken != null && !deviceToken.isEmpty()) { - deviceTokenService.saveOrActive(user.getId(), deviceToken); + deviceTokenService.saveOrActivate(user.getId(), deviceToken); } } @@ -107,4 +107,6 @@ private void validateUser(final Long userId) { throw new InvalidTokenException(); } } + + } diff --git a/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java b/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java index c5880a31..d21168c8 100644 --- a/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java +++ b/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java @@ -8,6 +8,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; @Service @Transactional @@ -16,7 +17,7 @@ public class DeviceTokenService { private final DeviceTokenRepository deviceTokenRepository; - public Long saveOrActive(final Long userId, final String token) { + public Long saveOrActivate(final Long userId, final String token) { final DeviceToken deviceToken = findOrPersistDeviceToken(userId, token); activateIfInactive(deviceToken); @@ -46,4 +47,9 @@ public ReadDeviceTokensDto readAllByUserId(final Long userId) { return ReadDeviceTokensDto.from(deviceTokens); } + + public void deactivate(final Long userId, final String token) { + final Optional deviceToken = deviceTokenRepository.findByUserIdAndToken(userId, token); + deviceToken.ifPresent(DeviceToken::deactivate); + } } diff --git a/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java b/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java index 89eabb09..2154daaa 100644 --- a/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java +++ b/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java @@ -26,7 +26,7 @@ class DeviceTokenServiceTest extends DeviceTokenServiceTestFixture { @Test void 디바이스_토큰을_저장한다() { // when - final Long actual = deviceTokenService.saveOrActive(사용자_아이디, 디바이스_토큰); + final Long actual = deviceTokenService.saveOrActivate(사용자_아이디, 디바이스_토큰); // then assertThat(actual).isPositive(); @@ -35,7 +35,7 @@ class DeviceTokenServiceTest extends DeviceTokenServiceTestFixture { @Test void 디바이스_토큰_저장시_비활성화된_동일한_토큰이_있다면_활성화한다() { // when - final Long actual = deviceTokenService.saveOrActive(사용자_아이디, 비활성화_디바이스_토큰.getToken()); + final Long actual = deviceTokenService.saveOrActivate(사용자_아이디, 비활성화_디바이스_토큰.getToken()); // then final DeviceToken deviceToken = deviceTokenRepository.findById(actual).get(); @@ -60,4 +60,14 @@ class DeviceTokenServiceTest extends DeviceTokenServiceTestFixture { softAssertions.assertThat(actual.deviceTokens().get(1).deviceToken()).isEqualTo(디바이스_토큰2.getToken()); }); } + + @Test + void 사용자의_특정_디바이스_토큰을_비활성화한다() { + // when + deviceTokenService.deactivate(사용자_아이디, 디바이스_토큰1.getToken()); + + // then + final DeviceToken actual = deviceTokenRepository.findByUserIdAndToken(사용자_아이디, 디바이스_토큰1.getToken()).get(); + assertThat(actual.isActive()).isFalse(); + } } From 8672b0cbaff7e0cc7f0495c61303acc12b9edfde Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 15:53:22 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EB=B8=94=EB=9E=99=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=86=A0=ED=81=B0=20=EB=B0=8F=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/domain/BlackListToken.java | 30 +++++++++++++++++ .../blacklist/BlackListTokenRepository.java | 11 +++++++ .../BlackListTokenRepositoryTest.java | 32 +++++++++++++++++++ .../BlackListTokenRepositoryTestFixture.java | 22 +++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java create mode 100644 src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java create mode 100644 src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java create mode 100644 src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java diff --git a/src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java b/src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java new file mode 100644 index 00000000..d02dce98 --- /dev/null +++ b/src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java @@ -0,0 +1,30 @@ +package com.backend.blooming.authentication.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@EqualsAndHashCode(of = "id", callSuper = false) +@ToString +@Table +public class BlackListToken { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String token; + + public BlackListToken(final String token) { + this.token = token; + } +} diff --git a/src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java b/src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java new file mode 100644 index 00000000..39be3dae --- /dev/null +++ b/src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java @@ -0,0 +1,11 @@ +package com.backend.blooming.authentication.infrastructure.blacklist; + +import com.backend.blooming.authentication.domain.BlackListToken; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface BlackListTokenRepository extends JpaRepository { + + Optional findByToken(final String token); +} diff --git a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java new file mode 100644 index 00000000..68f955a7 --- /dev/null +++ b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java @@ -0,0 +1,32 @@ +package com.backend.blooming.authentication.infrastructure.blacklist; + +import com.backend.blooming.authentication.domain.BlackListToken; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.Optional; + +@DataJpaTest +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class BlackListTokenRepositoryTest extends BlackListTokenRepositoryTestFixture { + + @Autowired + private BlackListTokenRepository blackListTokenRepository; + + @Test + void 블랙_리스트에_특정_토큰이_존재한다면_해당_토큰을_반환한다() { + // when + final Optional actual = blackListTokenRepository.findByToken(블랙_리스트_토큰.getToken()); + + // then + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(actual).isPresent(); + softAssertions.assertThat(actual.get().getToken()).isEqualTo(블랙_리스트_토큰.getToken()); + }); + } +} diff --git a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java new file mode 100644 index 00000000..5843f51c --- /dev/null +++ b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java @@ -0,0 +1,22 @@ +package com.backend.blooming.authentication.infrastructure.blacklist; + +import com.backend.blooming.authentication.domain.BlackListToken; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.LocalDateTime; + +@SuppressWarnings("NonAsciiCharacters") +public class BlackListTokenRepositoryTestFixture { + + @Autowired + private BlackListTokenRepository blackListTokenRepository; + + protected BlackListToken 블랙_리스트_토큰; + + @BeforeEach + void setUpFixture() { + 블랙_리스트_토큰 = new BlackListToken("black list token", LocalDateTime.now()); + blackListTokenRepository.save(블랙_리스트_토큰); + } +} From 18671224ba56fe86862a981381fa94ecc6f1d6b8 Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 17:06:59 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EB=B8=94=EB=9E=99=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=86=A0=ED=81=B0=EC=9D=84=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/BlackListTokenService.java | 30 ++++++++++++++++ ...lreadyRegisterBlackListTokenException.java | 11 ++++++ .../blacklist/BlackListTokenRepository.java | 2 ++ .../blooming/exception/ExceptionMessage.java | 3 ++ .../BlackListTokenServiceTest.java | 35 +++++++++++++++++++ .../BlackListTokenServiceTestFixture.java | 22 ++++++++++++ .../BlackListTokenRepositoryTest.java | 20 +++++++++++ .../BlackListTokenRepositoryTestFixture.java | 5 ++- 8 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/backend/blooming/authentication/application/BlackListTokenService.java create mode 100644 src/main/java/com/backend/blooming/authentication/application/exception/AlreadyRegisterBlackListTokenException.java create mode 100644 src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java create mode 100644 src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTestFixture.java diff --git a/src/main/java/com/backend/blooming/authentication/application/BlackListTokenService.java b/src/main/java/com/backend/blooming/authentication/application/BlackListTokenService.java new file mode 100644 index 00000000..94a27877 --- /dev/null +++ b/src/main/java/com/backend/blooming/authentication/application/BlackListTokenService.java @@ -0,0 +1,30 @@ +package com.backend.blooming.authentication.application; + +import com.backend.blooming.authentication.application.exception.AlreadyRegisterBlackListTokenException; +import com.backend.blooming.authentication.domain.BlackListToken; +import com.backend.blooming.authentication.infrastructure.blacklist.BlackListTokenRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class BlackListTokenService { + + private final BlackListTokenRepository blackListTokenRepository; + + public Long register(final String token) { + validateToken(token); + final BlackListToken blackListToken = new BlackListToken(token); + + return blackListTokenRepository.save(blackListToken) + .getId(); + } + + private void validateToken(String token) { + if (blackListTokenRepository.existsByToken(token)) { + throw new AlreadyRegisterBlackListTokenException(); + } + } +} diff --git a/src/main/java/com/backend/blooming/authentication/application/exception/AlreadyRegisterBlackListTokenException.java b/src/main/java/com/backend/blooming/authentication/application/exception/AlreadyRegisterBlackListTokenException.java new file mode 100644 index 00000000..30544917 --- /dev/null +++ b/src/main/java/com/backend/blooming/authentication/application/exception/AlreadyRegisterBlackListTokenException.java @@ -0,0 +1,11 @@ +package com.backend.blooming.authentication.application.exception; + +import com.backend.blooming.exception.BloomingException; +import com.backend.blooming.exception.ExceptionMessage; + +public class AlreadyRegisterBlackListTokenException extends BloomingException { + + public AlreadyRegisterBlackListTokenException() { + super(ExceptionMessage.ALREADY_REGISTER_BLACK_LIST_TOKEN); + } +} diff --git a/src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java b/src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java index 39be3dae..06efd02e 100644 --- a/src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java +++ b/src/main/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepository.java @@ -8,4 +8,6 @@ public interface BlackListTokenRepository extends JpaRepository { Optional findByToken(final String token); + + boolean existsByToken(final String token); } diff --git a/src/main/java/com/backend/blooming/exception/ExceptionMessage.java b/src/main/java/com/backend/blooming/exception/ExceptionMessage.java index e79bdd38..9c3da30e 100644 --- a/src/main/java/com/backend/blooming/exception/ExceptionMessage.java +++ b/src/main/java/com/backend/blooming/exception/ExceptionMessage.java @@ -16,6 +16,9 @@ public enum ExceptionMessage { EXPIRED_TOKEN("기한이 만료된 토큰입니다."), UNSUPPORTED_OAUTH_TYPE("지원하지 않는 소셜 로그인 방식입니다."), + // 블랙 리스트 토큰 + ALREADY_REGISTER_BLACK_LIST_TOKEN("이미 등록된 블랙 리스트 토큰입니다."), + // 사용자 NOT_FOUND_USER("사용자를 조회할 수 없습니다."), NULL_OR_EMPTY_EMAIL("이메일은 비어있을 수 없습니다."), diff --git a/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java b/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java new file mode 100644 index 00000000..54e4d1ec --- /dev/null +++ b/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java @@ -0,0 +1,35 @@ +package com.backend.blooming.authentication.application; + +import com.backend.blooming.authentication.application.exception.AlreadyRegisterBlackListTokenException; +import com.backend.blooming.configuration.IsolateDatabase; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.assertj.core.api.Assertions.*; + +@IsolateDatabase +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class BlackListTokenServiceTest extends BlackListTokenServiceTestFixture { + + @Autowired + private BlackListTokenService blackListTokenService; + + @Test + void 블랙_리스트_토큰을_등록한다() { + // when + final Long actual = blackListTokenService.register(토큰); + + // then + assertThat(actual).isPositive(); + } + + @Test + void 블랙_리스트_토큰_등록시_이미_등록된_토큰이라면_예외를_반환한다() { + // when & then + assertThatThrownBy(() -> blackListTokenService.register(이미_등록된_토큰)) + .isInstanceOf(AlreadyRegisterBlackListTokenException.class); + } +} diff --git a/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTestFixture.java b/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTestFixture.java new file mode 100644 index 00000000..a9a34939 --- /dev/null +++ b/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTestFixture.java @@ -0,0 +1,22 @@ +package com.backend.blooming.authentication.application; + +import com.backend.blooming.authentication.domain.BlackListToken; +import com.backend.blooming.authentication.infrastructure.blacklist.BlackListTokenRepository; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; + +@SuppressWarnings("NonAsciiCharacters") +public class BlackListTokenServiceTestFixture { + + @Autowired + private BlackListTokenRepository blackListTokenRepository; + + protected String 토큰 = "refresh token"; + protected String 이미_등록된_토큰 = "already register refresh token"; + + @BeforeEach + void setUpFixture() { + final BlackListToken 블랙_리스트_토큰 = new BlackListToken(이미_등록된_토큰); + blackListTokenRepository.save(블랙_리스트_토큰); + } +} diff --git a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java index 68f955a7..e9392a5f 100644 --- a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java +++ b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java @@ -10,6 +10,8 @@ import java.util.Optional; +import static org.assertj.core.api.Assertions.*; + @DataJpaTest @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @@ -29,4 +31,22 @@ class BlackListTokenRepositoryTest extends BlackListTokenRepositoryTestFixture { softAssertions.assertThat(actual.get().getToken()).isEqualTo(블랙_리스트_토큰.getToken()); }); } + + @Test + void 블랙_리스트에_특정_토큰이_존재한다면_참을_반환한다() { + // when + final boolean actual = blackListTokenRepository.existsByToken(블랙_리스트_토큰.getToken()); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 블랙_리스트에_특정_토큰이_존재하지_않다면_거짓을_반환한다() { + // when + final boolean actual = blackListTokenRepository.existsByToken(존재하지_않는_토큰); + + // then + assertThat(actual).isFalse(); + } } diff --git a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java index 5843f51c..3ac8e7ee 100644 --- a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java +++ b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTestFixture.java @@ -4,8 +4,6 @@ import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; -import java.time.LocalDateTime; - @SuppressWarnings("NonAsciiCharacters") public class BlackListTokenRepositoryTestFixture { @@ -13,10 +11,11 @@ public class BlackListTokenRepositoryTestFixture { private BlackListTokenRepository blackListTokenRepository; protected BlackListToken 블랙_리스트_토큰; + protected String 존재하지_않는_토큰 = "not exist token"; @BeforeEach void setUpFixture() { - 블랙_리스트_토큰 = new BlackListToken("black list token", LocalDateTime.now()); + 블랙_리스트_토큰 = new BlackListToken("black list token"); blackListTokenRepository.save(블랙_리스트_토큰); } } From 48dba0b0976c7a42e31a54745aad7e7cb4461ead Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 17:14:39 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B8=B0=EB=8A=A5=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthenticationService.java | 18 ++++++++++ .../application/dto/LogoutDto.java | 4 +++ .../AuthenticationServiceTest.java | 35 +++++++++++++++++++ .../AuthenticationServiceTestFixture.java | 29 +++++++++++---- 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java diff --git a/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java b/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java index 4e6bcf0d..0aafe86b 100644 --- a/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java +++ b/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java @@ -3,6 +3,7 @@ import com.backend.blooming.authentication.application.dto.LoginDto; import com.backend.blooming.authentication.application.dto.LoginInformationDto; import com.backend.blooming.authentication.application.dto.LoginUserInformationDto; +import com.backend.blooming.authentication.application.dto.LogoutDto; import com.backend.blooming.authentication.application.dto.TokenDto; import com.backend.blooming.authentication.application.util.OAuthClientComposite; import com.backend.blooming.authentication.infrastructure.exception.InvalidTokenException; @@ -13,6 +14,7 @@ import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; import com.backend.blooming.authentication.infrastructure.oauth.dto.UserInformationDto; import com.backend.blooming.devicetoken.application.service.DeviceTokenService; +import com.backend.blooming.user.application.exception.NotFoundUserException; import com.backend.blooming.user.domain.Email; import com.backend.blooming.user.domain.Name; import com.backend.blooming.user.domain.User; @@ -33,6 +35,7 @@ public class AuthenticationService { private final OAuthClientComposite oAuthClientComposite; private final TokenProvider tokenProvider; private final UserRepository userRepository; + private final BlackListTokenService blackListTokenService; private final DeviceTokenService deviceTokenService; public LoginInformationDto login(final OAuthType oAuthType, final LoginDto loginDto) { @@ -108,5 +111,20 @@ private void validateUser(final Long userId) { } } + public void logout(final Long userId, final LogoutDto logoutDto) { + final AuthClaims authClaims = tokenProvider.parseToken(TokenType.REFRESH, logoutDto.refreshToken()); + final User user = validateAndGetUser(userId, authClaims); + blackListTokenService.register(logoutDto.refreshToken()); + deviceTokenService.deactivate(user.getId(), logoutDto.deviceToken()); + } + + private User validateAndGetUser(final Long userId, final AuthClaims authClaims) { + if (!userId.equals(authClaims.userId())) { + throw new InvalidTokenException(); + } + + return userRepository.findById(userId) + .orElseThrow(NotFoundUserException::new); + } } diff --git a/src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java b/src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java new file mode 100644 index 00000000..b8712f62 --- /dev/null +++ b/src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java @@ -0,0 +1,4 @@ +package com.backend.blooming.authentication.application.dto; + +public record LogoutDto(String refreshToken, String deviceToken) { +} diff --git a/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java b/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java index f742eafc..fcc87e0c 100644 --- a/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java +++ b/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java @@ -2,13 +2,18 @@ import com.backend.blooming.authentication.application.dto.LoginInformationDto; import com.backend.blooming.authentication.application.dto.TokenDto; +import com.backend.blooming.authentication.domain.BlackListToken; +import com.backend.blooming.authentication.infrastructure.blacklist.BlackListTokenRepository; import com.backend.blooming.authentication.infrastructure.exception.InvalidTokenException; import com.backend.blooming.authentication.infrastructure.exception.OAuthException; import com.backend.blooming.authentication.infrastructure.exception.UnsupportedOAuthTypeException; import com.backend.blooming.authentication.infrastructure.oauth.OAuthClient; import com.backend.blooming.configuration.IsolateDatabase; +import com.backend.blooming.devicetoken.domain.DeviceToken; +import com.backend.blooming.devicetoken.infrastructure.repository.DeviceTokenRepository; import com.backend.blooming.user.domain.User; import com.backend.blooming.user.infrastructure.repository.UserRepository; +import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -34,6 +39,12 @@ class AuthenticationServiceTest extends AuthenticationServiceTestFixture { @Autowired private UserRepository userRepository; + @Autowired + private DeviceTokenRepository deviceTokenRepository; + + @Autowired + private BlackListTokenRepository blackListTokenRepository; + @Test void 로그인시_존재하지_않는_사용자인_경우_해당_사용자를_저장후_토큰_정보를_반환한다() { // given @@ -155,4 +166,28 @@ class AuthenticationServiceTest extends AuthenticationServiceTestFixture { assertThatThrownBy(() -> authenticationService.reissueAccessToken(유효하지_않는_타입의_refresh_token)) .isInstanceOf(InvalidTokenException.class); } + + @Test + void 로그아웃시_디바이스_토큰과_액세스_토큰을_비활성화한다() { + // when + authenticationService.logout(기존_사용자.getId(), 로그아웃_dto); + + // then + final BlackListToken blackListToken = blackListTokenRepository.findByToken(로그아웃_dto.refreshToken()).get(); + final DeviceToken deviceToken = deviceTokenRepository.findByUserIdAndToken( + 기존_사용자.getId(), + 로그아웃_dto.deviceToken() + ).get(); + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(blackListToken.getToken()).isEqualTo(로그아웃_dto.refreshToken()); + softAssertions.assertThat(deviceToken.isActive()).isFalse(); + }); + } + + @Test + void 로그아웃시_리프레시_토큰이_유효하지_않다면_예외를_발생시킨다() { + // when & then + assertThatThrownBy(() -> authenticationService.logout(기존_사용자.getId(), 유효하지_않은_리프레시_토큰을_갖는_로그아웃_dto)) + .isInstanceOf(InvalidTokenException.class); + } } diff --git a/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTestFixture.java b/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTestFixture.java index 5c157fd3..2f7e599f 100644 --- a/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTestFixture.java +++ b/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTestFixture.java @@ -1,11 +1,14 @@ package com.backend.blooming.authentication.application; import com.backend.blooming.authentication.application.dto.LoginDto; +import com.backend.blooming.authentication.application.dto.LogoutDto; import com.backend.blooming.authentication.infrastructure.jwt.TokenProvider; import com.backend.blooming.authentication.infrastructure.jwt.TokenType; import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; import com.backend.blooming.authentication.infrastructure.oauth.dto.UserInformationDto; import com.backend.blooming.authentication.infrastructure.oauth.kakao.dto.KakaoUserInformationDto; +import com.backend.blooming.devicetoken.domain.DeviceToken; +import com.backend.blooming.devicetoken.infrastructure.repository.DeviceTokenRepository; import com.backend.blooming.user.domain.Email; import com.backend.blooming.user.domain.Name; import com.backend.blooming.user.domain.User; @@ -22,6 +25,9 @@ public class AuthenticationServiceTestFixture { @Autowired private TokenProvider tokenProvider; + @Autowired + private DeviceTokenRepository deviceTokenRepository; + protected OAuthType oauth_타입 = OAuthType.KAKAO; protected String 소셜_액세스_토큰 = "social_access_token"; protected String 디바이스_토큰 = "device_token"; @@ -40,19 +46,28 @@ public class AuthenticationServiceTestFixture { protected String 존재하지_않는_사용자의_refresh_token; protected String 유효하지_않는_refresh_token = "Bearer invalid_refresh_token"; protected String 유효하지_않는_타입의_refresh_token = "refresh_token"; + protected User 기존_사용자; + protected LogoutDto 로그아웃_dto; + protected LogoutDto 유효하지_않은_리프레시_토큰을_갖는_로그아웃_dto; @BeforeEach void setUpFixture() { - final User 기존_사용자 = User.builder() - .oAuthType(oauth_타입) - .oAuthId(기존_사용자_소셜_정보.oAuthId()) - .name(new Name("기존 사용자")) - .email(new Email(기존_사용자_소셜_정보.email())) - .build(); - + 기존_사용자 = User.builder() + .oAuthType(oauth_타입) + .oAuthId(기존_사용자_소셜_정보.oAuthId()) + .name(new Name("기존 사용자")) + .email(new Email(기존_사용자_소셜_정보.email())) + .build(); userRepository.save(기존_사용자); + final DeviceToken 기존_사용자의_디바이스_토큰 = new DeviceToken(기존_사용자.getId(), "default_user_device_token"); + deviceTokenRepository.save(기존_사용자의_디바이스_토큰); + 유효한_refresh_token = "Bearer " + tokenProvider.createToken(TokenType.REFRESH, 기존_사용자.getId()); + final String 유효하지_않은_refresh_token = "Bearer " + tokenProvider.createToken(TokenType.REFRESH, 999L); + + 로그아웃_dto = new LogoutDto(유효한_refresh_token, 기존_사용자의_디바이스_토큰.getToken()); + 유효하지_않은_리프레시_토큰을_갖는_로그아웃_dto = new LogoutDto(유효하지_않은_refresh_token, 기존_사용자의_디바이스_토큰.getToken()); final long 존재하지_않는_사용자_아이디 = 9999L; 존재하지_않는_사용자의_refresh_token = "Bearer " + tokenProvider.createToken(TokenType.REFRESH, 존재하지_않는_사용자_아이디); From 9b550848bd5713b42469ed8d4ad850bb762f5140 Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:12:25 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B8=B0=EB=8A=A5=20=EC=BB=A8=ED=8A=B8=EB=A1=9C?= =?UTF-8?q?=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/LogoutDto.java | 6 +++ .../AuthenticationWebMvcConfiguration.java | 3 +- .../AuthenticationController.java | 15 +++++++ .../presentation/dto/LogoutRequest.java | 4 ++ .../AuthenticationControllerTest.java | 41 ++++++++++++++++++- .../AuthenticationControllerTestFixture.java | 13 ++++-- 6 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/backend/blooming/authentication/presentation/dto/LogoutRequest.java diff --git a/src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java b/src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java index b8712f62..0f4a427e 100644 --- a/src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java +++ b/src/main/java/com/backend/blooming/authentication/application/dto/LogoutDto.java @@ -1,4 +1,10 @@ package com.backend.blooming.authentication.application.dto; +import com.backend.blooming.authentication.presentation.dto.LogoutRequest; + public record LogoutDto(String refreshToken, String deviceToken) { + + public static LogoutDto from(final LogoutRequest logoutRequest) { + return new LogoutDto(logoutRequest.refreshToken(), logoutRequest.deviceToken()); + } } diff --git a/src/main/java/com/backend/blooming/authentication/configuration/AuthenticationWebMvcConfiguration.java b/src/main/java/com/backend/blooming/authentication/configuration/AuthenticationWebMvcConfiguration.java index ba9d45b3..0d96ad22 100644 --- a/src/main/java/com/backend/blooming/authentication/configuration/AuthenticationWebMvcConfiguration.java +++ b/src/main/java/com/backend/blooming/authentication/configuration/AuthenticationWebMvcConfiguration.java @@ -20,8 +20,7 @@ public class AuthenticationWebMvcConfiguration implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor) - .addPathPatterns("/**") - .excludePathPatterns("/auth/**"); + .addPathPatterns("/**"); } @Override diff --git a/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java b/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java index ce265928..d9d8b749 100644 --- a/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java +++ b/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java @@ -3,8 +3,12 @@ import com.backend.blooming.authentication.application.AuthenticationService; import com.backend.blooming.authentication.application.dto.LoginDto; import com.backend.blooming.authentication.application.dto.LoginInformationDto; +import com.backend.blooming.authentication.application.dto.LogoutDto; import com.backend.blooming.authentication.application.dto.TokenDto; import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; +import com.backend.blooming.authentication.presentation.anotaion.Authenticated; +import com.backend.blooming.authentication.presentation.argumentresolver.AuthenticatedUser; +import com.backend.blooming.authentication.presentation.dto.LogoutRequest; import com.backend.blooming.authentication.presentation.dto.request.ReissueAccessTokenRequest; import com.backend.blooming.authentication.presentation.dto.response.LoginInformationResponse; import com.backend.blooming.authentication.presentation.dto.response.SocialLoginRequest; @@ -45,4 +49,15 @@ public ResponseEntity reissueAccessToken( return ResponseEntity.ok(TokenResponse.from(tokenDto)); } + + @PostMapping(value = "/logout", headers = "X-API-VERSION=1") + public ResponseEntity logout( + @Authenticated final AuthenticatedUser authenticatedUser, + @RequestBody final LogoutRequest logoutRequest + ) { + authenticationService.logout(authenticatedUser.userId(), LogoutDto.from(logoutRequest)); + + return ResponseEntity.noContent() + .build(); + } } diff --git a/src/main/java/com/backend/blooming/authentication/presentation/dto/LogoutRequest.java b/src/main/java/com/backend/blooming/authentication/presentation/dto/LogoutRequest.java new file mode 100644 index 00000000..71c90aa1 --- /dev/null +++ b/src/main/java/com/backend/blooming/authentication/presentation/dto/LogoutRequest.java @@ -0,0 +1,4 @@ +package com.backend.blooming.authentication.presentation.dto; + +public record LogoutRequest(String refreshToken, String deviceToken) { +} diff --git a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java index a30a7288..ecd0de87 100644 --- a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java +++ b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java @@ -4,6 +4,7 @@ import com.backend.blooming.authentication.infrastructure.exception.InvalidTokenException; import com.backend.blooming.authentication.infrastructure.exception.OAuthException; import com.backend.blooming.authentication.infrastructure.jwt.TokenProvider; +import com.backend.blooming.authentication.infrastructure.jwt.TokenType; import com.backend.blooming.authentication.presentation.argumentresolver.AuthenticatedThreadLocal; import com.backend.blooming.common.RestDocsConfiguration; import com.backend.blooming.user.infrastructure.repository.UserRepository; @@ -16,6 +17,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.restdocs.payload.JsonFieldType; @@ -23,6 +25,7 @@ import static org.hamcrest.Matchers.is; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; @@ -31,12 +34,12 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(AuthenticationController.class) @Import({RestDocsConfiguration.class, AuthenticatedThreadLocal.class}) -@MockBean({TokenProvider.class, UserRepository.class}) @AutoConfigureRestDocs @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @@ -48,6 +51,12 @@ class AuthenticationControllerTest extends AuthenticationControllerTestFixture { @MockBean private AuthenticationService authenticationService; + @MockBean + private UserRepository userRepository; + + @MockBean + private TokenProvider tokenProvider; + @Autowired private ObjectMapper objectMapper; @@ -195,4 +204,34 @@ class AuthenticationControllerTest extends AuthenticationControllerTestFixture { jsonPath("$.message").exists() ); } + + @Test + void 로그아웃을_수행한다() throws Exception { + // given + given(tokenProvider.parseToken(TokenType.ACCESS, 소셜_액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_아이디)).willReturn(true); + willDoNothing().given(authenticationService).logout(사용자_아이디, 로그아웃_정보_dto); + + // when & then + mockMvc.perform(post("/auth/logout") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 소셜_액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(로그아웃_정보_요청)) + ).andExpectAll( + status().isNoContent() + ).andDo(print()).andDo( + restDocs.document( + requestHeaders( + headerWithName("X-API-VERSION").description("요청 버전"), + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ), + requestFields( + fieldWithPath("refreshToken").type(JsonFieldType.STRING) + .description("서비스 refresh token"), + fieldWithPath("deviceToken").type(JsonFieldType.STRING).description("서비스 device token") + ) + ) + ); + } } diff --git a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java index 2bf7ecb5..45b44d4d 100644 --- a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java +++ b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java @@ -2,8 +2,11 @@ import com.backend.blooming.authentication.application.dto.LoginDto; import com.backend.blooming.authentication.application.dto.LoginInformationDto; +import com.backend.blooming.authentication.application.dto.LogoutDto; import com.backend.blooming.authentication.application.dto.TokenDto; +import com.backend.blooming.authentication.infrastructure.jwt.dto.AuthClaims; import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; +import com.backend.blooming.authentication.presentation.dto.LogoutRequest; import com.backend.blooming.authentication.presentation.dto.request.ReissueAccessTokenRequest; import com.backend.blooming.authentication.presentation.dto.response.SocialLoginRequest; @@ -11,8 +14,8 @@ public class AuthenticationControllerTestFixture { protected OAuthType oauth_타입 = OAuthType.KAKAO; - private String 소셜_액세스_토큰 = "social_access_token"; - private String 디바이스_토큰 = "device_token"; + protected String 소셜_액세스_토큰 = "social_access_token"; + protected String 디바이스_토큰 = "device_token"; protected LoginDto 로그인_정보 = LoginDto.of(소셜_액세스_토큰, 디바이스_토큰); private String 유효하지_않은_소셜_액세스_토큰 = "social_access_token"; protected LoginDto 유효하지_않은_소셜_액세스_토큰을_가진_로그인_정보 = LoginDto.of(유효하지_않은_소셜_액세스_토큰, 디바이스_토큰); @@ -28,5 +31,9 @@ public class AuthenticationControllerTestFixture { ); protected String 서비스_refresh_token = "blooming_refresh_token"; protected ReissueAccessTokenRequest access_token_재발급_요청 = new ReissueAccessTokenRequest(서비스_refresh_token); - protected TokenDto 서비스_토큰_정보 = new TokenDto("access token", "refresh token"); + protected TokenDto 서비스_토큰_정보 = new TokenDto(소셜_액세스_토큰, "refresh token"); + protected Long 사용자_아이디 = 1L; + protected AuthClaims 사용자_토큰_정보 = new AuthClaims(사용자_아이디); + protected LogoutRequest 로그아웃_정보_요청 = new LogoutRequest(서비스_refresh_token, 디바이스_토큰); + protected LogoutDto 로그아웃_정보_dto = new LogoutDto(서비스_refresh_token, 디바이스_토큰); } From a1926ac71a3104e78bd67ef6a55536dab6ec239d Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:31:48 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=ED=83=88=ED=87=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthenticationService.java | 9 +++++++ .../service/DeviceTokenService.java | 5 ++++ .../AuthenticationServiceTest.java | 25 +++++++++++++++++++ .../service/DeviceTokenServiceTest.java | 12 +++++++++ 4 files changed, 51 insertions(+) diff --git a/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java b/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java index 0aafe86b..85a390be 100644 --- a/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java +++ b/src/main/java/com/backend/blooming/authentication/application/AuthenticationService.java @@ -127,4 +127,13 @@ private User validateAndGetUser(final Long userId, final AuthClaims authClaims) return userRepository.findById(userId) .orElseThrow(NotFoundUserException::new); } + + public void withdraw(final Long userId, final String refreshToken) { + final AuthClaims authClaims = tokenProvider.parseToken(TokenType.REFRESH, refreshToken); + final User user = validateAndGetUser(userId, authClaims); + + user.delete(); + blackListTokenService.register(refreshToken); + deviceTokenService.deactivateAllByUserId(user.getId()); + } } diff --git a/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java b/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java index d21168c8..f2afdd4a 100644 --- a/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java +++ b/src/main/java/com/backend/blooming/devicetoken/application/service/DeviceTokenService.java @@ -52,4 +52,9 @@ public void deactivate(final Long userId, final String token) { final Optional deviceToken = deviceTokenRepository.findByUserIdAndToken(userId, token); deviceToken.ifPresent(DeviceToken::deactivate); } + + public void deactivateAllByUserId(final Long userId) { + final List deviceTokens = deviceTokenRepository.findAllByUserIdAndActiveIsTrue(userId); + deviceTokens.forEach(DeviceToken::deactivate); + } } diff --git a/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java b/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java index fcc87e0c..1be3c203 100644 --- a/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java +++ b/src/test/java/com/backend/blooming/authentication/application/AuthenticationServiceTest.java @@ -20,6 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.BDDMockito.willReturn; @@ -190,4 +192,27 @@ class AuthenticationServiceTest extends AuthenticationServiceTestFixture { assertThatThrownBy(() -> authenticationService.logout(기존_사용자.getId(), 유효하지_않은_리프레시_토큰을_갖는_로그아웃_dto)) .isInstanceOf(InvalidTokenException.class); } + + @Test + void 로그아웃시_디바이스_토큰과_액세스_토큰을_비활성화하고_사용자의_삭제_여부를_참으로_변경한다() { + // when + authenticationService.withdraw(기존_사용자.getId(), 유효한_refresh_token); + + // then + final User user = userRepository.findById(기존_사용자.getId()).get(); + final BlackListToken blackListToken = blackListTokenRepository.findByToken(유효한_refresh_token).get(); + final List deviceTokens = deviceTokenRepository.findAllByUserIdAndActiveIsTrue(기존_사용자.getId()); + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(user.isDeleted()).isTrue(); + softAssertions.assertThat(blackListToken.getToken()).isEqualTo(유효한_refresh_token); + softAssertions.assertThat(deviceTokens).isEmpty(); + }); + } + + @Test + void 탈퇴시_리프레시_토큰이_유효하지_않다면_예외를_발생시킨다() { + // when & then + assertThatThrownBy(() -> authenticationService.withdraw(기존_사용자.getId(), 유효하지_않는_refresh_token)) + .isInstanceOf(InvalidTokenException.class); + } } diff --git a/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java b/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java index 2154daaa..6431823f 100644 --- a/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java +++ b/src/test/java/com/backend/blooming/devicetoken/application/service/DeviceTokenServiceTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; @IsolateDatabase @@ -70,4 +72,14 @@ class DeviceTokenServiceTest extends DeviceTokenServiceTestFixture { final DeviceToken actual = deviceTokenRepository.findByUserIdAndToken(사용자_아이디, 디바이스_토큰1.getToken()).get(); assertThat(actual.isActive()).isFalse(); } + + @Test + void 사용자의_모든_디바이스_토큰을_비활성화한다() { + // when + deviceTokenService.deactivateAllByUserId(사용자_아이디); + + // then + final List deviceTokens = deviceTokenRepository.findAllByUserIdAndActiveIsTrue(사용자_아이디); + assertThat(deviceTokens).isEmpty(); + } } From 0b506e6bfc3b3940a05cc47b2fc413f23f70ab99 Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:35:26 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=ED=83=88=ED=87=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuthenticationController.java | 13 ++++++++ .../presentation/dto/WithdrawRequest.java | 4 +++ .../AuthenticationControllerTest.java | 32 +++++++++++++++++-- .../AuthenticationControllerTestFixture.java | 2 ++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/backend/blooming/authentication/presentation/dto/WithdrawRequest.java diff --git a/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java b/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java index d9d8b749..dd008180 100644 --- a/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java +++ b/src/main/java/com/backend/blooming/authentication/presentation/AuthenticationController.java @@ -9,6 +9,7 @@ import com.backend.blooming.authentication.presentation.anotaion.Authenticated; import com.backend.blooming.authentication.presentation.argumentresolver.AuthenticatedUser; import com.backend.blooming.authentication.presentation.dto.LogoutRequest; +import com.backend.blooming.authentication.presentation.dto.WithdrawRequest; import com.backend.blooming.authentication.presentation.dto.request.ReissueAccessTokenRequest; import com.backend.blooming.authentication.presentation.dto.response.LoginInformationResponse; import com.backend.blooming.authentication.presentation.dto.response.SocialLoginRequest; @@ -16,6 +17,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -60,4 +62,15 @@ public ResponseEntity logout( return ResponseEntity.noContent() .build(); } + + @DeleteMapping(headers = "X-API-VERSION=1") + public ResponseEntity withdraw( + @Authenticated final AuthenticatedUser authenticatedUser, + @RequestBody final WithdrawRequest withdrawRequest + ) { + authenticationService.withdraw(authenticatedUser.userId(), withdrawRequest.refreshToken()); + + return ResponseEntity.noContent() + .build(); + } } diff --git a/src/main/java/com/backend/blooming/authentication/presentation/dto/WithdrawRequest.java b/src/main/java/com/backend/blooming/authentication/presentation/dto/WithdrawRequest.java new file mode 100644 index 00000000..28807263 --- /dev/null +++ b/src/main/java/com/backend/blooming/authentication/presentation/dto/WithdrawRequest.java @@ -0,0 +1,4 @@ +package com.backend.blooming.authentication.presentation.dto; + +public record WithdrawRequest(String refreshToken) { +} diff --git a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java index ecd0de87..3c1b4c52 100644 --- a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java +++ b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java @@ -28,6 +28,7 @@ import static org.mockito.BDDMockito.willDoNothing; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; @@ -227,11 +228,38 @@ class AuthenticationControllerTest extends AuthenticationControllerTestFixture { headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") ), requestFields( - fieldWithPath("refreshToken").type(JsonFieldType.STRING) - .description("서비스 refresh token"), + fieldWithPath("refreshToken").type(JsonFieldType.STRING).description("서비스 refresh token"), fieldWithPath("deviceToken").type(JsonFieldType.STRING).description("서비스 device token") ) ) ); } + + @Test + void 탈퇴를_수행한다() throws Exception { + // given + given(tokenProvider.parseToken(TokenType.ACCESS, 소셜_액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_아이디)).willReturn(true); + willDoNothing().given(authenticationService).withdraw(사용자_아이디, 서비스_refresh_token); + + // when & then + mockMvc.perform(delete("/auth") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 소셜_액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(탈퇴_정보_요청)) + ).andExpectAll( + status().isNoContent() + ).andDo(print()).andDo( + restDocs.document( + requestHeaders( + headerWithName("X-API-VERSION").description("요청 버전"), + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ), + requestFields( + fieldWithPath("refreshToken").type(JsonFieldType.STRING).description("서비스 refresh token") + ) + ) + ); + } } diff --git a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java index 45b44d4d..683dd877 100644 --- a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java +++ b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTestFixture.java @@ -7,6 +7,7 @@ import com.backend.blooming.authentication.infrastructure.jwt.dto.AuthClaims; import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; import com.backend.blooming.authentication.presentation.dto.LogoutRequest; +import com.backend.blooming.authentication.presentation.dto.WithdrawRequest; import com.backend.blooming.authentication.presentation.dto.request.ReissueAccessTokenRequest; import com.backend.blooming.authentication.presentation.dto.response.SocialLoginRequest; @@ -36,4 +37,5 @@ public class AuthenticationControllerTestFixture { protected AuthClaims 사용자_토큰_정보 = new AuthClaims(사용자_아이디); protected LogoutRequest 로그아웃_정보_요청 = new LogoutRequest(서비스_refresh_token, 디바이스_토큰); protected LogoutDto 로그아웃_정보_dto = new LogoutDto(서비스_refresh_token, 디바이스_토큰); + protected WithdrawRequest 탈퇴_정보_요청 = new WithdrawRequest(서비스_refresh_token); } From 388e94cc05f05463b9abff982026cd0b80bfa087 Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:48:19 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=20=EB=B8=94?= =?UTF-8?q?=EB=9E=99=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=EB=90=9C=20=ED=86=A0=ED=81=B0=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 11 ++++++++++ .../AuthenticationControllerTest.java | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java b/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java index 3ff0b32e..032ae857 100644 --- a/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package com.backend.blooming.exception; +import com.backend.blooming.authentication.application.exception.AlreadyRegisterBlackListTokenException; import com.backend.blooming.authentication.infrastructure.exception.InvalidTokenException; import com.backend.blooming.authentication.infrastructure.exception.OAuthException; import com.backend.blooming.authentication.infrastructure.exception.UnsupportedOAuthTypeException; @@ -170,4 +171,14 @@ public ResponseEntity handleDeleteFriendForbiddenException( return ResponseEntity.status(HttpStatus.FORBIDDEN) .body(new ExceptionResponse(exception.getMessage())); } + + @ExceptionHandler(AlreadyRegisterBlackListTokenException.class) + public ResponseEntity handleAlreadyRegisterBlackListTokenException( + final AlreadyRegisterBlackListTokenException exception + ) { + logger.warn(String.format(LOG_MESSAGE_FORMAT, exception.getClass().getSimpleName(), exception.getMessage())); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ExceptionResponse(exception.getMessage())); + } } diff --git a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java index 3c1b4c52..a59f9fd6 100644 --- a/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java +++ b/src/test/java/com/backend/blooming/authentication/presentation/AuthenticationControllerTest.java @@ -1,6 +1,7 @@ package com.backend.blooming.authentication.presentation; import com.backend.blooming.authentication.application.AuthenticationService; +import com.backend.blooming.authentication.application.exception.AlreadyRegisterBlackListTokenException; import com.backend.blooming.authentication.infrastructure.exception.InvalidTokenException; import com.backend.blooming.authentication.infrastructure.exception.OAuthException; import com.backend.blooming.authentication.infrastructure.jwt.TokenProvider; @@ -26,6 +27,7 @@ import static org.hamcrest.Matchers.is; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willThrow; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; @@ -235,6 +237,26 @@ class AuthenticationControllerTest extends AuthenticationControllerTestFixture { ); } + @Test + void 로그아웃을_수행시_이미_로그인했다면_400_예외를_반환한다() throws Exception { + // given + given(tokenProvider.parseToken(TokenType.ACCESS, 소셜_액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_아이디)).willReturn(true); + willThrow(new AlreadyRegisterBlackListTokenException()).given(authenticationService) + .logout(사용자_아이디, 로그아웃_정보_dto); + + // when & then + mockMvc.perform(post("/auth/logout") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 소셜_액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(로그아웃_정보_요청)) + ).andExpectAll( + status().isBadRequest(), + jsonPath("$.message").exists() + ).andDo(print()); + } + @Test void 탈퇴를_수행한다() throws Exception { // given From 9da2cb6434769dbae20d7787dc373ac68263693f Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:49:22 +0900 Subject: [PATCH 09/10] =?UTF-8?q?test:=20import=20=EB=AC=B8=EC=9D=98=20?= =?UTF-8?q?=EC=99=80=EC=9D=BC=EB=93=9C=20=EC=B9=B4=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/application/BlackListTokenServiceTest.java | 3 ++- .../infrastructure/blacklist/BlackListTokenRepositoryTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java b/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java index 54e4d1ec..1fcecea9 100644 --- a/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java +++ b/src/test/java/com/backend/blooming/authentication/application/BlackListTokenServiceTest.java @@ -7,7 +7,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; @IsolateDatabase @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) diff --git a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java index e9392a5f..48de368c 100644 --- a/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java +++ b/src/test/java/com/backend/blooming/authentication/infrastructure/blacklist/BlackListTokenRepositoryTest.java @@ -10,7 +10,7 @@ import java.util.Optional; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) From dda637b0fd9a043f41db3c24474f821548c5a72e Mon Sep 17 00:00:00 2001 From: JJ503 <63184334+JJ503@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:58:15 +0900 Subject: [PATCH 10/10] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/blooming/authentication/domain/BlackListToken.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java b/src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java index d02dce98..661b771d 100644 --- a/src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java +++ b/src/main/java/com/backend/blooming/authentication/domain/BlackListToken.java @@ -18,6 +18,7 @@ @ToString @Table public class BlackListToken { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;