diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index c4b9127d5..0df89426e 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -97,19 +97,18 @@ public ResponseEntity signUp( public ResponseEntity signOut( Authentication authentication ) { - String accessToken = (String) authentication.getCredentials(); - if (accessToken == null || accessToken.isBlank()) { - throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "토큰이 없습니다."); - } + String accessToken = getAccessToken(authentication); authService.signOut(accessToken); return ResponseEntity.ok().build(); } @PatchMapping("/quit") public ResponseEntity quit( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser SiteUser siteUser, + Authentication authentication // todo: #299를 작업하며 인자를 (Authentication authentication)만 받도록 수정해야 함 ) { - authService.quit(siteUser); + String accessToken = getAccessToken(authentication); + authService.quit(siteUser, accessToken); return ResponseEntity.ok().build(); } @@ -120,4 +119,11 @@ public ResponseEntity reissueToken( ReissueResponse reissueResponse = authService.reissue(reissueRequest); return ResponseEntity.ok(reissueResponse); } + + private String getAccessToken(Authentication authentication) { + if (authentication == null || !(authentication.getCredentials() instanceof String accessToken)) { + throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "엑세스 토큰이 없습니다."); + } + return accessToken; + } } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 86220d863..50926ca93 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -19,11 +19,13 @@ public class AuthService { private final AuthTokenProvider authTokenProvider; /* - * 로그아웃 한다. + * 로그아웃한다. * - 엑세스 토큰을 블랙리스트에 추가한다. + * - 리프레시 토큰을 삭제한다. * */ public void signOut(String token) { AccessToken accessToken = authTokenProvider.toAccessToken(token); + authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); authTokenProvider.addToBlacklist(accessToken); } @@ -31,11 +33,13 @@ public void signOut(String token) { * 탈퇴한다. * - 탈퇴한 시점의 다음날을 탈퇴일로 잡는다. * - e.g. 2024-01-01 18:00 탈퇴 시, 2024-01-02 00:00 가 탈퇴일이 된다. + * - 로그아웃한다. * */ @Transactional - public void quit(SiteUser siteUser) { + public void quit(SiteUser siteUser, String token) { // todo: #299를 작업하며 인자를 (String token)만 받도록 수정해야 함 LocalDate tomorrow = LocalDate.now().plusDays(1); siteUser.setQuitedAt(tomorrow); + signOut(token); } /* diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index d44f3bc8f..2e43e8be9 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -49,6 +49,12 @@ public boolean isValidRefreshToken(String requestedRefreshToken) { return Objects.equals(requestedRefreshToken, foundRefreshToken); } + public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { + String subject = accessToken.subject().value(); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); + redisTemplate.delete(refreshTokenKey); + } + @Override public boolean isTokenBlacklisted(String accessToken) { String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken); diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 759c90d88..18c7c4ef0 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -1,5 +1,6 @@ package com.example.solidconnection.auth.service; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.custom.exception.CustomException; @@ -12,12 +13,14 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; import java.time.LocalDate; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; @DisplayName("인증 서비스 테스트") @TestContainerSpringBootTest @@ -32,29 +35,44 @@ class AuthServiceTest { @Autowired private SiteUserRepository siteUserRepository; + @Autowired + private RedisTemplate redisTemplate; + @Test void 로그아웃한다() { // given - AccessToken accessToken = authTokenProvider.generateAccessToken(new Subject("subject")); // todo: #296 + Subject subject = new Subject("subject"); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 // when authService.signOut(accessToken.token()); // then - assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue(); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + assertAll( + () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), + () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + ); } @Test void 탈퇴한다() { // given SiteUser siteUser = createSiteUser(); + Subject subject = authTokenProvider.toSubject(siteUser); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 // when - authService.quit(siteUser); + authService.quit(siteUser, accessToken.token()); // then LocalDate tomorrow = LocalDate.now().plusDays(1); - assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + assertAll( + () -> assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow), + () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), + () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + ); } @Nested diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index 2a0bd9b73..dc35ab3a7 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -69,6 +69,20 @@ class 리프레시_토큰을_제공한다 { () -> assertThat(authTokenProvider.isValidRefreshToken(fakeRefreshToken.token())).isFalse() ); } + + @Test + void 액세서_토큰에_해당하는_리프레시_토큰을_삭제한다() { + // given + authTokenProvider.generateAndSaveRefreshToken(subject); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); + + // when + authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); + + // then + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(); + } } @Nested