From b61bed83859f9b98043b9b40aac951436e4fea76 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sat, 23 Aug 2025 12:12:52 +0900 Subject: [PATCH 01/23] =?UTF-8?q?refactor:=20Token=20VO=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/auth/{service => domain}/AccessToken.java | 2 +- .../auth/{service => domain}/RefreshToken.java | 2 +- .../solidconnection/auth/{service => domain}/Subject.java | 2 +- .../com/example/solidconnection/auth/dto/ReissueResponse.java | 2 +- .../com/example/solidconnection/auth/dto/SignInResponse.java | 4 ++-- .../com/example/solidconnection/auth/service/AuthService.java | 1 + .../solidconnection/auth/service/AuthTokenProvider.java | 3 +++ .../example/solidconnection/auth/service/SignInService.java | 2 ++ .../solidconnection/auth/token/TokenBlackListService.java | 2 +- .../example/solidconnection/auth/service/AuthServiceTest.java | 2 ++ .../solidconnection/auth/service/AuthTokenProviderTest.java | 2 ++ .../auth/service/TokenBlackListServiceTest.java | 1 + .../websocket/WebSocketStompIntegrationTest.java | 2 +- 13 files changed, 19 insertions(+), 8 deletions(-) rename src/main/java/com/example/solidconnection/auth/{service => domain}/AccessToken.java (84%) rename src/main/java/com/example/solidconnection/auth/{service => domain}/RefreshToken.java (78%) rename src/main/java/com/example/solidconnection/auth/{service => domain}/Subject.java (50%) diff --git a/src/main/java/com/example/solidconnection/auth/service/AccessToken.java b/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java similarity index 84% rename from src/main/java/com/example/solidconnection/auth/service/AccessToken.java rename to src/main/java/com/example/solidconnection/auth/domain/AccessToken.java index 3456a2171..7fe419d43 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AccessToken.java +++ b/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.domain; import com.example.solidconnection.siteuser.domain.Role; diff --git a/src/main/java/com/example/solidconnection/auth/service/RefreshToken.java b/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java similarity index 78% rename from src/main/java/com/example/solidconnection/auth/service/RefreshToken.java rename to src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java index 2aac3ad8c..b0ea6db43 100644 --- a/src/main/java/com/example/solidconnection/auth/service/RefreshToken.java +++ b/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.domain; public record RefreshToken( Subject subject, diff --git a/src/main/java/com/example/solidconnection/auth/service/Subject.java b/src/main/java/com/example/solidconnection/auth/domain/Subject.java similarity index 50% rename from src/main/java/com/example/solidconnection/auth/service/Subject.java rename to src/main/java/com/example/solidconnection/auth/domain/Subject.java index 15e5c6c75..3a0e29448 100644 --- a/src/main/java/com/example/solidconnection/auth/service/Subject.java +++ b/src/main/java/com/example/solidconnection/auth/domain/Subject.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.domain; public record Subject( String value diff --git a/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java b/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java index 972470cca..434198c32 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.auth.dto; -import com.example.solidconnection.auth.service.AccessToken; +import com.example.solidconnection.auth.domain.AccessToken; public record ReissueResponse( String accessToken diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java b/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java index b01fdd369..ac9d39290 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.auth.dto; -import com.example.solidconnection.auth.service.AccessToken; -import com.example.solidconnection.auth.service.RefreshToken; +import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.domain.RefreshToken; public record SignInResponse( String 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 01c162002..0aeafe4ad 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -3,6 +3,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; +import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; 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 8e55f77d4..1f1fe6f09 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -2,6 +2,9 @@ import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; +import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.domain.RefreshToken; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.Role; diff --git a/src/main/java/com/example/solidconnection/auth/service/SignInService.java b/src/main/java/com/example/solidconnection/auth/service/SignInService.java index 16ec4c484..4643a63b1 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignInService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.auth.service; +import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.domain.RefreshToken; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java index 7f208710c..cf50590a8 100644 --- a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -2,7 +2,7 @@ import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; -import com.example.solidconnection.auth.service.AccessToken; +import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.security.filter.BlacklistChecker; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; 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 caedec489..1d704e587 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -5,6 +5,8 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; +import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.domain.RefreshToken; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.token.TokenBlackListService; 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 54dce4f68..40e0f78a7 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -3,6 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.domain.RefreshToken; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java index 5267f88f3..e70572dad 100644 --- a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -3,6 +3,7 @@ import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; import static org.assertj.core.api.Assertions.assertThat; +import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.support.TestContainerSpringBootTest; diff --git a/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java index b39c91ece..330b084dd 100644 --- a/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java +++ b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java @@ -4,7 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.example.solidconnection.auth.service.AccessToken; +import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.service.AuthTokenProvider; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; From 4b3d8111046e2c7aa02ffe2ba8a1177090ee590a Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sat, 23 Aug 2025 22:00:05 +0900 Subject: [PATCH 02/23] =?UTF-8?q?refactor:=20Token=20VO=EC=9D=98=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1=EC=9D=84=20=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - '토큰을 객체로 만들 때, 실제로 그 토큰이 가진 성질을 100% 반영해야 하는가?'에 대해서 고민이 되었다. 그런데 실제로 코드에서 토큰이 사용되는 것은 "문자열로 구성된 발급"이 대부분이므로, 토큰이 실제로 가지는 claim들을 100% 구현하는 것은 오히려 복잡도를 증가시킨다는 생각이 들었다. --- .../example/solidconnection/auth/domain/AccessToken.java | 7 ------- .../example/solidconnection/auth/domain/RefreshToken.java | 4 ---- .../solidconnection/auth/service/AuthTokenProvider.java | 6 +++--- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java b/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java index 7fe419d43..3e93786f5 100644 --- a/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java +++ b/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java @@ -1,14 +1,7 @@ package com.example.solidconnection.auth.domain; -import com.example.solidconnection.siteuser.domain.Role; - public record AccessToken( - Subject subject, - Role role, String token ) { - public AccessToken(String subject, Role role, String token) { - this(new Subject(subject), role, token); - } } diff --git a/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java b/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java index b0ea6db43..9efd4e99e 100644 --- a/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java +++ b/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java @@ -1,11 +1,7 @@ package com.example.solidconnection.auth.domain; public record RefreshToken( - Subject subject, String token ) { - RefreshToken(String subject, String token) { - this(new Subject(subject), 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 1f1fe6f09..a7f546b25 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -34,14 +34,14 @@ public AccessToken generateAccessToken(SiteUser siteUser) { Map.of(ROLE_CLAIM_KEY, role.name()), TokenType.ACCESS ); - return new AccessToken(subject, role, token); + return new AccessToken(token); } public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { Subject subject = toSubject(siteUser); String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH); tokenProvider.saveToken(token, TokenType.REFRESH); - return new RefreshToken(subject, token); + return new RefreshToken(token); } /* @@ -57,7 +57,7 @@ public boolean isValidRefreshToken(String requestedRefreshToken) { } public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { - String subject = accessToken.subject().value(); + String subject = tokenProvider.parseSubject(accessToken.token()); String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); redisTemplate.delete(refreshTokenKey); } From 332be93455b1feb706a63bf1029d119c5bb120e8 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sat, 23 Aug 2025 22:00:45 +0900 Subject: [PATCH 03/23] =?UTF-8?q?test:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EC=97=90=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/AuthServiceTest.java | 9 +++++++-- .../auth/service/AuthTokenProviderTest.java | 15 +++++++++++---- .../auth/service/TokenBlackListServiceTest.java | 4 +--- 3 files changed, 19 insertions(+), 9 deletions(-) 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 1d704e587..09a02324d 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -45,13 +45,18 @@ class AuthServiceTest { @Autowired private SiteUserRepository siteUserRepository; + @Autowired + private TokenProvider tokenProvider; + private SiteUser siteUser; private AccessToken accessToken; + private String expectedSubject; @BeforeEach void setUp() { siteUser = siteUserFixture.사용자(); accessToken = authTokenProvider.generateAccessToken(siteUser); + expectedSubject = tokenProvider.parseSubject(accessToken.token()); } @Test @@ -60,7 +65,7 @@ void setUp() { authService.signOut(accessToken.token()); // then - String refreshTokenKey = TokenType.REFRESH.addPrefix(accessToken.subject().value()); + String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); assertAll( () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() @@ -74,7 +79,7 @@ void setUp() { // then LocalDate tomorrow = LocalDate.now().plusDays(1); - String refreshTokenKey = TokenType.REFRESH.addPrefix(accessToken.subject().value()); + String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); SiteUser actualSitUser = siteUserRepository.findById(siteUser.getId()).orElseThrow(); assertAll( () -> assertThat(actualSitUser.getQuitedAt()).isEqualTo(tomorrow), 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 40e0f78a7..3f823fa9a 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -29,6 +29,9 @@ class AuthTokenProviderTest { @Autowired private SiteUserFixture siteUserFixture; + @Autowired + private TokenProvider tokenProvider; + private SiteUser siteUser; private String expectedSubject; @@ -44,10 +47,13 @@ void setUp() { AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser); // then + String accessTokenValue = accessToken.token(); + String actualSubject = tokenProvider.parseSubject(accessTokenValue); + String actualRole = tokenProvider.parseClaims(accessTokenValue).get("role", String.class); assertAll( - () -> assertThat(accessToken.subject().value()).isEqualTo(expectedSubject), - () -> assertThat(accessToken.role()).isEqualTo(siteUser.getRole()), - () -> assertThat(accessToken.token()).isNotNull() + () -> assertThat(accessTokenValue).isNotNull(), + () -> assertThat(actualSubject).isEqualTo(expectedSubject), + () -> assertThat(actualRole).isEqualTo(siteUser.getRole().toString()) ); } @@ -61,9 +67,10 @@ class 리프레시_토큰을_제공한다 { // then String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); + String actualSubject = tokenProvider.parseSubject(actualRefreshToken.token()); String expectedRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); assertAll( - () -> assertThat(actualRefreshToken.subject().value()).isEqualTo(expectedSubject), + () -> assertThat(actualSubject).isEqualTo(expectedSubject), () -> assertThat(actualRefreshToken.token()).isEqualTo(expectedRefreshToken) ); } diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java index e70572dad..dbe6f5acc 100644 --- a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -5,7 +5,6 @@ import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.token.TokenBlackListService; -import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -28,10 +27,9 @@ class TokenBlackListServiceTest { @BeforeEach void setUp() { - accessToken = new AccessToken("subject", Role.MENTEE, "token"); + accessToken = new AccessToken("tokenValue"); } - @Test void 액세스_토큰을_블랙리스트에_추가한다() { // when From 3a4cc07b8083905babfe4c782cb115963cbb61e6 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sat, 23 Aug 2025 23:34:16 +0900 Subject: [PATCH 04/23] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20'?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=86=8C'=EC=99=80=20'=EC=A0=9C=EA=B3=B5?= =?UTF-8?q?=EC=9E=90'=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/AuthTokenProvider.java | 14 ++-- .../auth/service/SignUpTokenProvider.java | 38 +++------ .../auth/service/TokenProvider.java | 2 - .../auth/service/TokenStorage.java | 13 +++ .../auth/token/JwtTokenProvider.java | 3 - .../auth/token/RedisTokenStorage.java | 50 +++++++++++ .../auth/token/RedisTokenStorageTest.java | 83 +++++++++++++++++++ 7 files changed, 162 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/service/TokenStorage.java create mode 100644 src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java create mode 100644 src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java 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 a7f546b25..eee1e405f 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -13,7 +13,6 @@ import java.util.Map; import java.util.Objects; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @@ -22,8 +21,8 @@ public class AuthTokenProvider { private static final String ROLE_CLAIM_KEY = "role"; - private final RedisTemplate redisTemplate; private final TokenProvider tokenProvider; + private final TokenStorage tokenStorage; private final SiteUserRepository siteUserRepository; public AccessToken generateAccessToken(SiteUser siteUser) { @@ -40,7 +39,7 @@ public AccessToken generateAccessToken(SiteUser siteUser) { public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { Subject subject = toSubject(siteUser); String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH); - tokenProvider.saveToken(token, TokenType.REFRESH); + tokenStorage.saveToken(token, TokenType.REFRESH); return new RefreshToken(token); } @@ -51,15 +50,14 @@ public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { * */ public boolean isValidRefreshToken(String requestedRefreshToken) { String subject = tokenProvider.parseSubject(requestedRefreshToken); - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); - String foundRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); - return Objects.equals(requestedRefreshToken, foundRefreshToken); + return tokenStorage.findToken(subject, TokenType.REFRESH) + .map(foundRefreshToken -> Objects.equals(foundRefreshToken, requestedRefreshToken)) + .orElse(false); } public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { String subject = tokenProvider.parseSubject(accessToken.token()); - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); - redisTemplate.delete(refreshTokenKey); + tokenStorage.deleteToken(subject, TokenType.REFRESH); } public SiteUser parseSiteUser(String token) { diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java index 05480b10d..a0ea7ce03 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java @@ -4,18 +4,10 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import java.util.Date; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @@ -24,28 +16,20 @@ public class SignUpTokenProvider { private static final String AUTH_TYPE_CLAIM_KEY = "authType"; - private final JwtProperties jwtProperties; - private final RedisTemplate redisTemplate; private final TokenProvider tokenProvider; + private final TokenStorage tokenStorage; public String generateAndSaveSignUpToken(String email, AuthType authType) { - Map authTypeClaim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, authType)); - Claims claims = Jwts.claims(authTypeClaim).setSubject(email); - Date now = new Date(); - Date expiredDate = new Date(now.getTime() + TokenType.SIGN_UP.getExpireTime()); - - String signUpToken = Jwts.builder() - .setClaims(claims) - .setIssuedAt(now) - .setExpiration(expiredDate) - .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) - .compact(); - return tokenProvider.saveToken(signUpToken, TokenType.SIGN_UP); + String token = tokenProvider.generateToken( + email, + Map.of(AUTH_TYPE_CLAIM_KEY, authType.toString()), + TokenType.SIGN_UP + ); + return tokenStorage.saveToken(token, TokenType.SIGN_UP); } public void deleteByEmail(String email) { - String key = TokenType.SIGN_UP.addPrefix(email); - redisTemplate.delete(key); + tokenStorage.deleteToken(email, TokenType.SIGN_UP); } public void validateSignUpToken(String token) { @@ -66,10 +50,8 @@ private void validateFormatAndExpiration(String token) { // 파싱되는지, Aut } private void validateIssuedByServer(String email) { - String key = TokenType.SIGN_UP.addPrefix(email); - if (redisTemplate.opsForValue().get(key) == null) { - throw new CustomException(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER); - } + tokenStorage.findToken(email, TokenType.SIGN_UP) + .orElseThrow(() -> new CustomException(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER)); } public String parseEmail(String token) { diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index 22120b084..fd5fc4937 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -10,8 +10,6 @@ public interface TokenProvider { String generateToken(String string, Map claims, TokenType tokenType); - String saveToken(String token, TokenType tokenType); - String parseSubject(String token); Claims parseClaims(String token); diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenStorage.java b/src/main/java/com/example/solidconnection/auth/service/TokenStorage.java new file mode 100644 index 000000000..0f27756a5 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/TokenStorage.java @@ -0,0 +1,13 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.domain.TokenType; +import java.util.Optional; + +public interface TokenStorage { + + String saveToken(String token, TokenType tokenType); + + Optional findToken(String subject, TokenType tokenType); + + void deleteToken(String subject, TokenType tokenType); +} diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index d7c968ccf..17c0c93f4 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -11,9 +11,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.Map; -import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @@ -21,7 +19,6 @@ public class JwtTokenProvider implements TokenProvider { private final JwtProperties jwtProperties; - private final RedisTemplate redisTemplate; @Override public final String generateToken(String string, TokenType tokenType) { diff --git a/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java b/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java new file mode 100644 index 000000000..988336d0c --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java @@ -0,0 +1,50 @@ +package com.example.solidconnection.auth.token; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.service.TokenStorage; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RedisTokenStorage implements TokenStorage { + + private final TokenProvider tokenProvider; + private final RedisTemplate redisTemplate; + + @Override + public String saveToken(String token, TokenType tokenType) { + String subject = tokenProvider.parseSubject(token); + redisTemplate.opsForValue().set( + createKey(subject, tokenType), + token, + tokenType.getExpireTime(), + TimeUnit.MILLISECONDS + ); + return token; + } + + @Override + public Optional findToken(String subject, TokenType tokenType) { + String key = createKey(subject, tokenType); + String foundTokenValue = redisTemplate.opsForValue().get(key); + if (foundTokenValue == null || foundTokenValue.isBlank()) { + return Optional.empty(); + } + return Optional.of(foundTokenValue); + } + + @Override + public void deleteToken(String subject, TokenType tokenType) { + String key = createKey(subject, tokenType); + redisTemplate.delete(key); + } + + private String createKey(String subject, TokenType tokenType) { + return tokenType.addPrefix(subject); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java b/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java new file mode 100644 index 000000000..3a9a42858 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java @@ -0,0 +1,83 @@ +package com.example.solidconnection.auth.token; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@TestContainerSpringBootTest +@DisplayName("Redis 토큰 저장소 테스트") +class RedisTokenStorageTest { + + @Autowired + private RedisTokenStorage redisTokenStorage; + + @Autowired + private TokenProvider tokenProvider; + + private final String subject = "subject123"; + private final TokenType tokenType = TokenType.ACCESS; + + @Test + void 토큰을_저장한다() { + // given + String expectedToken = tokenProvider.generateToken(subject, tokenType); + + // when + String savedToken = redisTokenStorage.saveToken(expectedToken, tokenType); + + // then + Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + assertAll( + () -> assertThat(foundToken).hasValue(expectedToken), + () -> assertThat(savedToken).isEqualTo(expectedToken) + ); + } + + @Nested + class 토큰을_조회한다 { + + @Test + void 저장된_토큰이_있으면_Optional에_담아_반한다() { + // given + String expectedToken = tokenProvider.generateToken(subject, tokenType); + redisTokenStorage.saveToken(expectedToken, tokenType); + + // when + Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + + // then + assertThat(foundToken).hasValue(expectedToken); + } + + @Test + void 저장된_토큰이_없으면_빈_Optional을_반환한다() { + // when + Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + + // then + assertThat(foundToken).isEmpty(); + } + } + + @Test + void 토큰을_삭제한다() { + // given + String expectedToken = tokenProvider.generateToken(subject, tokenType); + redisTokenStorage.saveToken(expectedToken, tokenType); + + // when + redisTokenStorage.deleteToken(subject, tokenType); + + // then + Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + assertThat(foundToken).isEmpty(); + } +} From 7bf9d9d9efbb12b225c616e725a13faae48e7efd Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sat, 23 Aug 2025 23:36:22 +0900 Subject: [PATCH 05/23] =?UTF-8?q?refactor:=20TokenProvider=EA=B0=80=20jwt?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=8B=9C=EA=B7=B8?= =?UTF-8?q?=EB=8B=88=EC=B2=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/SignUpTokenProvider.java | 9 +++------ .../auth/service/TokenProvider.java | 3 +-- .../auth/token/JwtTokenProvider.java | 18 +++++------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java index a0ea7ce03..9685f5448 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java @@ -40,9 +40,7 @@ public void validateSignUpToken(String token) { private void validateFormatAndExpiration(String token) { // 파싱되는지, AuthType이 포함되어있는지 검증 try { - Claims claims = tokenProvider.parseClaims(token); - Objects.requireNonNull(claims.getSubject()); - String serializedAuthType = claims.get(AUTH_TYPE_CLAIM_KEY, String.class); + String serializedAuthType = tokenProvider.parseClaims(token, AUTH_TYPE_CLAIM_KEY, String.class); AuthType.valueOf(serializedAuthType); } catch (Exception e) { throw new CustomException(SIGN_UP_TOKEN_INVALID); @@ -59,8 +57,7 @@ public String parseEmail(String token) { } public AuthType parseAuthType(String token) { - Claims claims = tokenProvider.parseClaims(token); - String authTypeStr = claims.get(AUTH_TYPE_CLAIM_KEY, String.class); - return AuthType.valueOf(authTypeStr); + String serializedAuthType = tokenProvider.parseClaims(token, AUTH_TYPE_CLAIM_KEY, String.class); + return AuthType.valueOf(serializedAuthType); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index fd5fc4937..15ccbf34b 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -1,7 +1,6 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import io.jsonwebtoken.Claims; import java.util.Map; public interface TokenProvider { @@ -12,5 +11,5 @@ public interface TokenProvider { String parseSubject(String token); - Claims parseClaims(String token); + T parseClaims(String token, String claimName, Class claimType); } diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index 17c0c93f4..a43716087 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -44,24 +44,16 @@ private String generateJwtTokenValue(String subject, Map claims, } @Override - public final String saveToken(String token, TokenType tokenType) { - String subject = parseSubject(token); - redisTemplate.opsForValue().set( - tokenType.addPrefix(subject), - token, - tokenType.getExpireTime(), - TimeUnit.MILLISECONDS - ); - return token; + public String parseSubject(String token) { + return parseJwtClaims(token).getSubject(); } @Override - public String parseSubject(String token) { - return parseClaims(token).getSubject(); + public T parseClaims(String token, String claimName, Class claimType) { + return parseJwtClaims(token).get(claimName, claimType); } - @Override - public Claims parseClaims(String token) { + private Claims parseJwtClaims(String token) { try { return Jwts.parser() .setSigningKey(jwtProperties.secret()) From 5a1ee42de515d9421f538407f51c77fea68e7667 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 00:08:25 +0900 Subject: [PATCH 06/23] =?UTF-8?q?test:=20TokenStorage=EC=99=80=20TokenProv?= =?UTF-8?q?ider=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=EC=97=90=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auth 하위의 테스트 코드에 'RedisTemplate' autowired 존재하지 않게 - io.jsonwebtoken.Claims 에 대한 참조는 반드시 필요한 곳에만 존재하게 --- .../auth/service/AuthServiceTest.java | 8 +- .../auth/service/AuthTokenProviderTest.java | 27 ++-- .../auth/service/JwtTokenProviderTest.java | 117 ++++++++---------- .../auth/service/SignInServiceTest.java | 8 +- .../auth/service/SignUpTokenProviderTest.java | 67 ++++------ 5 files changed, 90 insertions(+), 137 deletions(-) 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 09a02324d..caf016422 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -21,7 +21,6 @@ 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; @DisplayName("인증 서비스 테스트") @TestContainerSpringBootTest @@ -37,7 +36,7 @@ class AuthServiceTest { private TokenBlackListService tokenBlackListService; @Autowired - private RedisTemplate redisTemplate; + private TokenStorage tokenStorage; @Autowired private SiteUserFixture siteUserFixture; @@ -65,9 +64,8 @@ void setUp() { authService.signOut(accessToken.token()); // then - String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); assertAll( - () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), + () -> assertThat(tokenStorage.findToken(expectedSubject, TokenType.REFRESH)).isEmpty(), () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } @@ -83,7 +81,7 @@ void setUp() { SiteUser actualSitUser = siteUserRepository.findById(siteUser.getId()).orElseThrow(); assertAll( () -> assertThat(actualSitUser.getQuitedAt()).isEqualTo(tomorrow), - () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), + () -> assertThat(tokenStorage.findToken(expectedSubject, TokenType.REFRESH)).isEmpty(), () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } 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 3f823fa9a..849940abb 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -6,15 +6,16 @@ import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.domain.RefreshToken; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; 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; @TestContainerSpringBootTest @DisplayName("인증 토큰 제공자 테스트") @@ -24,13 +25,13 @@ class AuthTokenProviderTest { private AuthTokenProvider authTokenProvider; @Autowired - private RedisTemplate redisTemplate; + private TokenProvider tokenProvider; @Autowired - private SiteUserFixture siteUserFixture; + private TokenStorage tokenStorage; @Autowired - private TokenProvider tokenProvider; + private SiteUserFixture siteUserFixture; private SiteUser siteUser; private String expectedSubject; @@ -49,11 +50,11 @@ void setUp() { // then String accessTokenValue = accessToken.token(); String actualSubject = tokenProvider.parseSubject(accessTokenValue); - String actualRole = tokenProvider.parseClaims(accessTokenValue).get("role", String.class); + Role actualRole = authTokenProvider.parseSiteUser(accessTokenValue).getRole(); assertAll( () -> assertThat(accessTokenValue).isNotNull(), () -> assertThat(actualSubject).isEqualTo(expectedSubject), - () -> assertThat(actualRole).isEqualTo(siteUser.getRole().toString()) + () -> assertThat(actualRole).isEqualTo(siteUser.getRole()) ); } @@ -63,15 +64,14 @@ class 리프레시_토큰을_제공한다 { @Test void 리프레시_토큰을_생성하고_저장한다() { // when - RefreshToken actualRefreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); + RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); // then - String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); - String actualSubject = tokenProvider.parseSubject(actualRefreshToken.token()); - String expectedRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); + String actualSubject = tokenProvider.parseSubject(refreshToken.token()); + Optional savedRefreshToken = tokenStorage.findToken(expectedSubject, TokenType.REFRESH); assertAll( - () -> assertThat(actualSubject).isEqualTo(expectedSubject), - () -> assertThat(actualRefreshToken.token()).isEqualTo(expectedRefreshToken) + () -> assertThat(savedRefreshToken).hasValue(refreshToken.token()), + () -> assertThat(actualSubject).isEqualTo(expectedSubject) ); } @@ -98,8 +98,7 @@ class 리프레시_토큰을_제공한다 { authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); // then - String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); - assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(); + assertThat(tokenStorage.findToken(expectedSubject, TokenType.REFRESH)).isEmpty(); } } diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index 62655df2a..591802b27 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -20,7 +20,6 @@ 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; @DisplayName("토큰 제공자 테스트") @TestContainerSpringBootTest @@ -32,68 +31,59 @@ class JwtTokenProviderTest { @Autowired private JwtProperties jwtProperties; - @Autowired - private RedisTemplate redisTemplate; - @Nested class 토큰을_생성한다 { @Test void subject_만_있는_토큰을_생성한다() { // given - String actualSubject = "subject123"; - TokenType actualTokenType = TokenType.ACCESS; + String expectedSubject = "subject123"; + TokenType expectedTokenType = TokenType.ACCESS; // when - String token = tokenProvider.generateToken(actualSubject, actualTokenType); + String token = tokenProvider.generateToken(expectedSubject, expectedTokenType); // then - subject와 만료 시간이 일치하는지 검증 - Claims claims = tokenProvider.parseClaims(token); - long expectedExpireTime = claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + String actualSubject = tokenProvider.parseSubject(token); + long actualExpireTime = getActualExpireTime(token); assertAll( - () -> assertThat(claims.getSubject()).isEqualTo(actualSubject), - () -> assertThat(expectedExpireTime).isEqualTo(actualTokenType.getExpireTime()) + () -> assertThat(actualSubject).isEqualTo(expectedSubject), + () -> assertThat(actualExpireTime).isEqualTo(expectedTokenType.getExpireTime()) ); } @Test void subject_와_claims_가_있는_토큰을_생성한다() { // given - String actualSubject = "subject123"; - Map customClaims = Map.of("key1", "value1", "key2", "value2"); - TokenType actualTokenType = TokenType.ACCESS; + String expectedSubject = "subject123"; + String key1 = "key1"; + String value1 = "value1"; + String key2 = "key2"; + String value2 = "value2"; + Map customClaims = Map.of(key1, value1, key2, value2); + TokenType expectedTokenType = TokenType.ACCESS; // when - String token = tokenProvider.generateToken(actualSubject, customClaims, actualTokenType); + String token = tokenProvider.generateToken(expectedSubject, customClaims, expectedTokenType); // then - subject와 커스텀 클레임이 일치하는지 검증 - Claims claims = tokenProvider.parseClaims(token); - long expectedExpireTime = claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + String actualSubject = tokenProvider.parseSubject(token); + long actualExpireTime = getActualExpireTime(token); assertAll( - () -> assertThat(claims.getSubject()).isEqualTo(actualSubject), - () -> assertThat(claims).containsAllEntriesOf(customClaims), - () -> assertThat(expectedExpireTime).isEqualTo(actualTokenType.getExpireTime()) + () -> assertThat(actualSubject).isEqualTo(expectedSubject), + () -> assertThat(actualExpireTime).isEqualTo(expectedTokenType.getExpireTime()), + () -> assertThat(tokenProvider.parseClaims(token, key1, String.class)).isEqualTo(value1), + () -> assertThat(tokenProvider.parseClaims(token, key2, String.class)).isEqualTo(value2) ); } - } - @Test - void 토큰을_저장한다() { - // given - String subject = "subject123"; - TokenType tokenType = TokenType.ACCESS; - String token = tokenProvider.generateToken(subject, tokenType); - - // when - String savedToken = tokenProvider.saveToken(token, tokenType); - - // then - key: "{TokenType.Prefix}:subject", value: {token} 로 저장되어있는지 검증, 반환하는 값이 value와 같은지 검증 - String key = tokenType.addPrefix(subject); - String value = redisTemplate.opsForValue().get(key); - assertAll( - () -> assertThat(value).isEqualTo(token), - () -> assertThat(savedToken).isEqualTo(value) - ); + private long getActualExpireTime(String token) { + Claims claims = Jwts.parser() + .setSigningKey(jwtProperties.secret()) + .parseClaimsJws(token) + .getBody(); + return claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + } } @Nested @@ -103,7 +93,7 @@ class 토큰으로부터_subject_를_추출한다 { void 유효한_토큰의_subject_를_추출한다() { // given String subject = "subject000"; - String token = createValidToken(subject); + String token = tokenProvider.generateToken(subject, TokenType.SIGN_UP); // when String extractedSubject = tokenProvider.parseSubject(token); @@ -128,55 +118,46 @@ class 토큰으로부터_subject_를_추출한다 { @Nested class 토큰으로부터_claim_을_추출한다 { + private final String subject = "subject"; + private final String claimKey = "key"; + private final String claimValue = "value"; + @Test void 유효한_토큰의_claim_을_추출한다() { // given - String subject = "subject"; - String claimKey = "key"; - String claimValue = "value"; - Claims expectedClaims = Jwts.claims(new HashMap<>(Map.of(claimKey, claimValue))).setSubject(subject); - String token = createValidToken(expectedClaims); + String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), TokenType.SIGN_UP); // when - Claims actualClaims = tokenProvider.parseClaims(token); + String actualClaimValue = tokenProvider.parseClaims(token, claimKey, String.class); // then - assertAll( - () -> assertThat(actualClaims.getSubject()).isEqualTo(subject), - () -> assertThat(actualClaims.get(claimKey)).isEqualTo(claimValue) - ); + assertThat(actualClaimValue).isEqualTo(claimValue); } @Test void 유효하지_않은_토큰의_claim_을_추출하면_예외가_발생한다() { // given - String subject = "subject"; - Claims expectedClaims = Jwts.claims().setSubject(subject); + Claims expectedClaims = Jwts.claims(new HashMap<>(Map.of(claimKey, claimValue))); String token = createExpiredToken(expectedClaims); // when - assertThatCode(() -> tokenProvider.parseClaims(token)) + assertThatCode(() -> tokenProvider.parseClaims(token, claimKey, String.class)) .isInstanceOf(CustomException.class) .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); } - } - private String createValidToken(String subject) { - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) - .compact(); - } + @Test + void 존재하지_않는_claim_을_추출하면_null을_반환한다() { + // given + String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), TokenType.SIGN_UP); + String nonExistentClaimKey = "nonExistentKey"; - private String createValidToken(Claims claims) { - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) - .compact(); + // when + String actualClaimValue = tokenProvider.parseClaims(token, nonExistentClaimKey, String.class); + + // then + assertThat(actualClaimValue).isNull(); + } } private String createExpiredToken(String subject) { diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java index da06aa3e4..9a5d72b44 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java @@ -9,11 +9,11 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.time.LocalDate; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; @DisplayName("로그인 서비스 테스트") @TestContainerSpringBootTest @@ -26,7 +26,7 @@ class SignInServiceTest { private TokenProvider tokenProvider; @Autowired - private RedisTemplate redisTemplate; + private TokenStorage tokenStorage; @Autowired private SiteUserFixture siteUserFixture; @@ -48,11 +48,11 @@ void setUp() { // then String accessTokenSubject = tokenProvider.parseSubject(signInResponse.accessToken()); String refreshTokenSubject = tokenProvider.parseSubject(signInResponse.refreshToken()); - String savedRefreshToken = redisTemplate.opsForValue().get(TokenType.REFRESH.addPrefix(refreshTokenSubject)); + Optional savedRefreshToken = tokenStorage.findToken(subject, TokenType.REFRESH); assertAll( () -> assertThat(accessTokenSubject).isEqualTo(subject), () -> assertThat(refreshTokenSubject).isEqualTo(subject), - () -> assertThat(savedRefreshToken).isEqualTo(signInResponse.refreshToken())); + () -> assertThat(savedRefreshToken).hasValue(signInResponse.refreshToken())); } @Test diff --git a/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java index c75eac5f5..ebbe87d7f 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java @@ -2,27 +2,28 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static java.util.Optional.empty; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.given; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.support.TestContainerSpringBootTest; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; 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 org.springframework.boot.test.mock.mockito.SpyBean; @TestContainerSpringBootTest @DisplayName("회원가입 토큰 제공자 테스트") @@ -34,8 +35,8 @@ class SignUpTokenProviderTest { @Autowired private TokenProvider tokenProvider; - @Autowired - private RedisTemplate redisTemplate; + @SpyBean + private TokenStorage tokenStorage; @Autowired private JwtProperties jwtProperties; @@ -50,14 +51,13 @@ class SignUpTokenProviderTest { String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); // then - Claims claims = tokenProvider.parseClaims(signUpToken); - String actualSubject = claims.getSubject(); - AuthType actualAuthType = AuthType.valueOf(claims.get(authTypeClaimKey, String.class)); - String signUpTokenKey = TokenType.SIGN_UP.addPrefix(email); + String actualSubject = tokenProvider.parseSubject(signUpToken); + String actualAuthType = tokenProvider.parseClaims(signUpToken, authTypeClaimKey, String.class); + Optional actualSavedToken = tokenStorage.findToken(email, TokenType.SIGN_UP); assertAll( () -> assertThat(actualSubject).isEqualTo(email), - () -> assertThat(actualAuthType).isEqualTo(authType), - () -> assertThat(redisTemplate.opsForValue().get(signUpTokenKey)).isEqualTo(signUpToken) + () -> assertThat(actualAuthType).isEqualTo(authType.toString()), + () -> assertThat(actualSavedToken).hasValue(signUpToken) ); } @@ -70,8 +70,7 @@ class SignUpTokenProviderTest { signUpTokenProvider.deleteByEmail(email); // then - String signUpTokenKey = TokenType.SIGN_UP.addPrefix(email); - assertThat(redisTemplate.opsForValue().get(signUpTokenKey)).isNull(); + assertThat(tokenStorage.findToken(email, TokenType.SIGN_UP)).isEmpty(); } @Nested @@ -80,9 +79,7 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 검증_성공한다() { // given - Map claim = new HashMap<>(Map.of(authTypeClaimKey, authType)); - String validToken = createBaseJwtBuilder().setSubject(email).addClaims(claim).compact(); - redisTemplate.opsForValue().set(TokenType.SIGN_UP.addPrefix(email), validToken); + String validToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); // when & then assertThatCode(() -> signUpTokenProvider.validateSignUpToken(validToken)).doesNotThrowAnyException(); @@ -114,8 +111,8 @@ class 주어진_회원가입_토큰을_검증한다 { void 정해진_형식에_맞지_않으면_예외가_발생한다_authType_클래스_불일치() { // given String wrongAuthType = "카카오"; - Map wrongClaim = new HashMap<>(Map.of(authTypeClaimKey, wrongAuthType)); - String wrongAuthTypeClaim = createBaseJwtBuilder().addClaims(wrongClaim).compact(); + Map wrongClaim = new HashMap<>(Map.of(authTypeClaimKey, wrongAuthType)); + String wrongAuthTypeClaim = tokenProvider.generateToken(email, wrongClaim, TokenType.SIGN_UP); // when & then assertThatCode(() -> signUpTokenProvider.validateSignUpToken(wrongAuthTypeClaim)) @@ -123,23 +120,11 @@ class 주어진_회원가입_토큰을_검증한다 { .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } - @Test - void 정해진_형식에_맞지_않으면_예외가_발생한다_subject_누락() { - // given - Map claim = new HashMap<>(Map.of(authTypeClaimKey, authType)); - String noSubject = createBaseJwtBuilder().addClaims(claim).compact(); - - // when & then - assertThatCode(() -> signUpTokenProvider.validateSignUpToken(noSubject)) - .isInstanceOf(CustomException.class) - .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); - } - @Test void 우리_서버에_발급된_토큰이_아니면_예외가_발생한다() { // given - Map validClaim = new HashMap<>(Map.of(authTypeClaimKey, authType)); - String signUpToken = createBaseJwtBuilder().addClaims(validClaim).setSubject(email).compact(); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + given(tokenStorage.findToken(email, TokenType.SIGN_UP)).willReturn(empty()); // when & then assertThatCode(() -> signUpTokenProvider.validateSignUpToken(signUpToken)) @@ -151,12 +136,10 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 회원가입_토큰에서_이메일을_추출한다() { // given - Map claim = Map.of(authTypeClaimKey, authType); - String validToken = createBaseJwtBuilder().setSubject(email).addClaims(claim).compact(); - redisTemplate.opsForValue().set(TokenType.SIGN_UP.addPrefix(email), validToken); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); // when - String extractedEmail = signUpTokenProvider.parseEmail(validToken); + String extractedEmail = signUpTokenProvider.parseEmail(signUpToken); // then assertThat(extractedEmail).isEqualTo(email); @@ -165,11 +148,10 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 회원가입_토큰에서_인증_타입을_추출한다() { // given - Map claim = Map.of(authTypeClaimKey, authType); - String validToken = createBaseJwtBuilder().setSubject(email).addClaims(claim).compact(); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); // when - AuthType extractedAuthType = signUpTokenProvider.parseAuthType(validToken); + AuthType extractedAuthType = signUpTokenProvider.parseAuthType(signUpToken); // then assertThat(extractedAuthType).isEqualTo(authType); @@ -183,11 +165,4 @@ private String createExpiredToken() { .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) .compact(); } - - private JwtBuilder createBaseJwtBuilder() { - return Jwts.builder() - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()); - } } From 1319eaebec6e90db8667446b93abf851eaa1701b Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 00:15:16 +0900 Subject: [PATCH 07/23] =?UTF-8?q?refactor:=20SignUpToken=20VO=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1,=20Token=20VO=EA=B0=80=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/solidconnection/auth/domain/AccessToken.java | 2 +- .../example/solidconnection/auth/domain/RefreshToken.java | 2 +- .../example/solidconnection/auth/domain/SignUpToken.java | 7 +++++++ .../com/example/solidconnection/auth/domain/Token.java | 5 +++++ .../solidconnection/auth/service/SignUpTokenProvider.java | 6 ++++-- 5 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/domain/SignUpToken.java create mode 100644 src/main/java/com/example/solidconnection/auth/domain/Token.java diff --git a/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java b/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java index 3e93786f5..07df18ff6 100644 --- a/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java +++ b/src/main/java/com/example/solidconnection/auth/domain/AccessToken.java @@ -2,6 +2,6 @@ public record AccessToken( String token -) { +) implements Token { } diff --git a/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java b/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java index 9efd4e99e..aa0680ae7 100644 --- a/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java +++ b/src/main/java/com/example/solidconnection/auth/domain/RefreshToken.java @@ -2,6 +2,6 @@ public record RefreshToken( String token -) { +) implements Token { } diff --git a/src/main/java/com/example/solidconnection/auth/domain/SignUpToken.java b/src/main/java/com/example/solidconnection/auth/domain/SignUpToken.java new file mode 100644 index 000000000..aed55920c --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/domain/SignUpToken.java @@ -0,0 +1,7 @@ +package com.example.solidconnection.auth.domain; + +public record SignUpToken( + String token +) implements Token { + +} diff --git a/src/main/java/com/example/solidconnection/auth/domain/Token.java b/src/main/java/com/example/solidconnection/auth/domain/Token.java new file mode 100644 index 000000000..d10f4cddf --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/domain/Token.java @@ -0,0 +1,5 @@ +package com.example.solidconnection.auth.domain; + +public interface Token { + +} diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java index 9685f5448..e39f1e4fe 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java @@ -3,6 +3,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import com.example.solidconnection.auth.domain.SignUpToken; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; @@ -19,13 +20,14 @@ public class SignUpTokenProvider { private final TokenProvider tokenProvider; private final TokenStorage tokenStorage; - public String generateAndSaveSignUpToken(String email, AuthType authType) { + public SignUpToken generateAndSaveSignUpToken(String email, AuthType authType) { String token = tokenProvider.generateToken( email, Map.of(AUTH_TYPE_CLAIM_KEY, authType.toString()), TokenType.SIGN_UP ); - return tokenStorage.saveToken(token, TokenType.SIGN_UP); + tokenStorage.saveToken(token, TokenType.SIGN_UP); + return new SignUpToken(token); } public void deleteByEmail(String email) { From 15709d6491d518fa739183cfb66389bf368e8737 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 00:18:42 +0900 Subject: [PATCH 08/23] =?UTF-8?q?refactor:=20signup=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=95=98=EB=82=98=EC=9D=98=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20=EC=9C=84=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 4 ++-- .../auth/service/oauth/OAuthService.java | 7 ++++--- .../{ => signup}/EmailSignUpTokenProvider.java | 4 ++-- .../{ => signup}/PasswordTemporaryStorage.java | 2 +- .../auth/service/{ => signup}/SignUpService.java | 3 ++- .../service/{ => signup}/SignUpTokenProvider.java | 4 +++- .../{ => signup}/PasswordTemporaryStorageTest.java | 2 +- .../{ => signup}/SignUpTokenProviderTest.java | 14 ++++++++------ 8 files changed, 23 insertions(+), 17 deletions(-) rename src/main/java/com/example/solidconnection/auth/service/{ => signup}/EmailSignUpTokenProvider.java (93%) rename src/main/java/com/example/solidconnection/auth/service/{ => signup}/PasswordTemporaryStorage.java (96%) rename src/main/java/com/example/solidconnection/auth/service/{ => signup}/SignUpService.java (97%) rename src/main/java/com/example/solidconnection/auth/service/{ => signup}/SignUpTokenProvider.java (92%) rename src/test/java/com/example/solidconnection/auth/service/{ => signup}/PasswordTemporaryStorageTest.java (96%) rename src/test/java/com/example/solidconnection/auth/service/{ => signup}/SignUpTokenProviderTest.java (94%) 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 f5a30bb2f..136f88e26 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -11,8 +11,8 @@ import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse; import com.example.solidconnection.auth.service.AuthService; import com.example.solidconnection.auth.service.EmailSignInService; -import com.example.solidconnection.auth.service.EmailSignUpTokenProvider; -import com.example.solidconnection.auth.service.SignUpService; +import com.example.solidconnection.auth.service.signup.EmailSignUpTokenProvider; +import com.example.solidconnection.auth.service.signup.SignUpService; import com.example.solidconnection.auth.service.oauth.OAuthService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java index 9343bfa21..0b43a7753 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java @@ -1,5 +1,6 @@ package com.example.solidconnection.auth.service.oauth; +import com.example.solidconnection.auth.domain.SignUpToken; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.oauth.OAuthCodeRequest; import com.example.solidconnection.auth.dto.oauth.OAuthResponse; @@ -7,7 +8,7 @@ import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; import com.example.solidconnection.auth.dto.oauth.SignUpPrepareResponse; import com.example.solidconnection.auth.service.SignInService; -import com.example.solidconnection.auth.service.SignUpTokenProvider; +import com.example.solidconnection.auth.service.signup.SignUpTokenProvider; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -50,7 +51,7 @@ private OAuthSignInResponse getSignInResponse(SiteUser siteUser) { } private SignUpPrepareResponse getSignUpPrepareResponse(OAuthUserInfoDto userInfoDto, AuthType authType) { - String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), authType); - return SignUpPrepareResponse.of(userInfoDto, signUpToken); + SignUpToken signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), authType); + return SignUpPrepareResponse.of(userInfoDto, signUpToken.token()); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/signup/EmailSignUpTokenProvider.java similarity index 93% rename from src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java rename to src/main/java/com/example/solidconnection/auth/service/signup/EmailSignUpTokenProvider.java index a3e2e5dc9..0f9d4281a 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/EmailSignUpTokenProvider.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signup; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; import com.example.solidconnection.common.exception.CustomException; @@ -27,6 +27,6 @@ public String issueEmailSignUpToken(EmailSignUpTokenRequest request) { } passwordTemporaryStorage.save(email, password); - return signUpTokenProvider.generateAndSaveSignUpToken(email, AuthType.EMAIL); + return signUpTokenProvider.generateAndSaveSignUpToken(email, AuthType.EMAIL).token(); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/PasswordTemporaryStorage.java b/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java similarity index 96% rename from src/main/java/com/example/solidconnection/auth/service/PasswordTemporaryStorage.java rename to src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java index adcb8bf68..46d9db157 100644 --- a/src/main/java/com/example/solidconnection/auth/service/PasswordTemporaryStorage.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signup; import com.example.solidconnection.auth.domain.TokenType; import java.util.Optional; diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpService.java similarity index 97% rename from src/main/java/com/example/solidconnection/auth/service/SignUpService.java rename to src/main/java/com/example/solidconnection/auth/service/signup/SignUpService.java index d6feed9e1..ec0b63522 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpService.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signup; import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; @@ -6,6 +6,7 @@ import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; +import com.example.solidconnection.auth.service.SignInService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.location.country.service.InterestedCountryService; import com.example.solidconnection.location.region.service.InterestedRegionService; diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java similarity index 92% rename from src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java rename to src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java index e39f1e4fe..75163e91b 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java @@ -1,10 +1,12 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signup; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import com.example.solidconnection.auth.domain.SignUpToken; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.service.TokenStorage; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; import java.util.Map; diff --git a/src/test/java/com/example/solidconnection/auth/service/PasswordTemporaryStorageTest.java b/src/test/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorageTest.java similarity index 96% rename from src/test/java/com/example/solidconnection/auth/service/PasswordTemporaryStorageTest.java rename to src/test/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorageTest.java index ea3ed6355..2a56f94b3 100644 --- a/src/test/java/com/example/solidconnection/auth/service/PasswordTemporaryStorageTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorageTest.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signup; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java similarity index 94% rename from src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java rename to src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java index ebbe87d7f..d38f562be 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signup; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; @@ -9,6 +9,8 @@ import static org.mockito.BDDMockito.given; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.service.TokenStorage; import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; @@ -48,7 +50,7 @@ class SignUpTokenProviderTest { @Test void 회원가입_토큰을_생성하고_저장한다() { // when - String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType).token(); // then String actualSubject = tokenProvider.parseSubject(signUpToken); @@ -79,7 +81,7 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 검증_성공한다() { // given - String validToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + String validToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType).token(); // when & then assertThatCode(() -> signUpTokenProvider.validateSignUpToken(validToken)).doesNotThrowAnyException(); @@ -123,7 +125,7 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 우리_서버에_발급된_토큰이_아니면_예외가_발생한다() { // given - String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType).token(); given(tokenStorage.findToken(email, TokenType.SIGN_UP)).willReturn(empty()); // when & then @@ -136,7 +138,7 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 회원가입_토큰에서_이메일을_추출한다() { // given - String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType).token(); // when String extractedEmail = signUpTokenProvider.parseEmail(signUpToken); @@ -148,7 +150,7 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 회원가입_토큰에서_인증_타입을_추출한다() { // given - String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType).token(); // when AuthType extractedAuthType = signUpTokenProvider.parseAuthType(signUpToken); From 0aa559a89cc484b410e930009f5815ce29c69683 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 00:20:59 +0900 Subject: [PATCH 09/23] =?UTF-8?q?refactor:=20signin=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=95=98=EB=82=98=EC=9D=98=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20=EC=9C=84=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/auth/controller/AuthController.java | 2 +- .../solidconnection/auth/service/oauth/OAuthService.java | 2 +- .../auth/service/{ => signin}/EmailSignInService.java | 2 +- .../auth/service/{ => signin}/SignInService.java | 3 ++- .../solidconnection/auth/service/signup/SignUpService.java | 2 +- .../auth/service/{ => signin}/EmailSignInServiceTest.java | 2 +- .../auth/service/{ => signin}/SignInServiceTest.java | 4 +++- 7 files changed, 10 insertions(+), 7 deletions(-) rename src/main/java/com/example/solidconnection/auth/service/{ => signin}/EmailSignInService.java (96%) rename src/main/java/com/example/solidconnection/auth/service/{ => signin}/SignInService.java (89%) rename src/test/java/com/example/solidconnection/auth/service/{ => signin}/EmailSignInServiceTest.java (97%) rename src/test/java/com/example/solidconnection/auth/service/{ => signin}/SignInServiceTest.java (92%) 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 136f88e26..6ac7a3224 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -10,7 +10,7 @@ import com.example.solidconnection.auth.dto.oauth.OAuthResponse; import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse; import com.example.solidconnection.auth.service.AuthService; -import com.example.solidconnection.auth.service.EmailSignInService; +import com.example.solidconnection.auth.service.signin.EmailSignInService; import com.example.solidconnection.auth.service.signup.EmailSignUpTokenProvider; import com.example.solidconnection.auth.service.signup.SignUpService; import com.example.solidconnection.auth.service.oauth.OAuthService; diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java index 0b43a7753..08ab0c0b7 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java @@ -7,7 +7,7 @@ import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse; import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; import com.example.solidconnection.auth.dto.oauth.SignUpPrepareResponse; -import com.example.solidconnection.auth.service.SignInService; +import com.example.solidconnection.auth.service.signin.SignInService; import com.example.solidconnection.auth.service.signup.SignUpTokenProvider; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java b/src/main/java/com/example/solidconnection/auth/service/signin/EmailSignInService.java similarity index 96% rename from src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java rename to src/main/java/com/example/solidconnection/auth/service/signin/EmailSignInService.java index 4dac56586..29f379a22 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/signin/EmailSignInService.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signin; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_IN_FAILED; diff --git a/src/main/java/com/example/solidconnection/auth/service/SignInService.java b/src/main/java/com/example/solidconnection/auth/service/signin/SignInService.java similarity index 89% rename from src/main/java/com/example/solidconnection/auth/service/SignInService.java rename to src/main/java/com/example/solidconnection/auth/service/signin/SignInService.java index 4643a63b1..ee63a02c3 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/signin/SignInService.java @@ -1,8 +1,9 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signin; import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.domain.RefreshToken; import com.example.solidconnection.auth.dto.SignInResponse; +import com.example.solidconnection.auth.service.AuthTokenProvider; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpService.java index ec0b63522..86415d913 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpService.java @@ -6,7 +6,7 @@ import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; -import com.example.solidconnection.auth.service.SignInService; +import com.example.solidconnection.auth.service.signin.SignInService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.location.country.service.InterestedCountryService; import com.example.solidconnection.location.region.service.InterestedRegionService; diff --git a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/signin/EmailSignInServiceTest.java similarity index 97% rename from src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java rename to src/test/java/com/example/solidconnection/auth/service/signin/EmailSignInServiceTest.java index 04b6780ad..46c6d565a 100644 --- a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/signin/EmailSignInServiceTest.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signin; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/signin/SignInServiceTest.java similarity index 92% rename from src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java rename to src/test/java/com/example/solidconnection/auth/service/signin/SignInServiceTest.java index 9a5d72b44..aa0d07307 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/signin/SignInServiceTest.java @@ -1,10 +1,12 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.auth.service.signin; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.SignInResponse; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.service.TokenStorage; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; From 25e94f550b4d9fd42ec4b03e2ad3b4b49d305ad0 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 01:58:52 +0900 Subject: [PATCH 10/23] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=B3=80=EC=88=98=EB=A5=BC=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=80=EC=88=98=EB=A1=9C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/token/config/TokenConfig.java | 8 ++++ .../auth/token/config/TokenProperties.java | 37 +++++++++++++++++++ src/test/resources/application.yml | 11 ++++++ 3 files changed, 56 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java create mode 100644 src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java diff --git a/src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java b/src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java new file mode 100644 index 000000000..7cc64766b --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java @@ -0,0 +1,8 @@ +package com.example.solidconnection.auth.token.config; + +public record TokenConfig( + String storageKeyPrefix, + long expireTime +) { + +} diff --git a/src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java b/src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java new file mode 100644 index 000000000..38fd604cb --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java @@ -0,0 +1,37 @@ +package com.example.solidconnection.auth.token.config; + +import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.domain.RefreshToken; +import com.example.solidconnection.auth.domain.SignUpToken; +import com.example.solidconnection.auth.domain.Token; +import jakarta.annotation.PostConstruct; +import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "token") +public record TokenProperties( + TokenConfig access, + TokenConfig refresh, + TokenConfig signUp, + TokenConfig blackList +) { + + private static Map, TokenConfig> tokenConfigs; + + @PostConstruct + public void init() { + tokenConfigs = Map.of( + AccessToken.class, access, + RefreshToken.class, refresh, + SignUpToken.class, signUp + ); + } + + public static long getExpireTime(Class tokenClass) { + return tokenConfigs.get(tokenClass).expireTime(); + } + + public static String getStorageKeyPrefix(Class tokenClass) { + return tokenConfigs.get(tokenClass).storageKeyPrefix(); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index ce5a848cb..06e370314 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -86,5 +86,16 @@ cors: news: default-thumbnail-url: "default-thumbnail-url" token: + access: + expire-time: 600000 # 10minutes + storage-key-prefix: "ACCESS" refresh: cookie-domain: "test.domain.com" + expire-time: 600000 # 10minutes + storage-key-prefix: "REFRESH" + sign-up: + expire-time: 600000 # 10minutes + storage-key-prefix: "SIGN_UP" + black-list: + expire-time: 600000 # 10minutes + storage-key-prefix: "BLACKLIST" From 57ba58c6e916c1986efd1ec35c2981483d5e69b2 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 02:16:00 +0900 Subject: [PATCH 11/23] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C,=20TokenType=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토큰과 관련된 설정 값은 TokenType이 아니라 환경변수를 사용하도록 점진적으로 변경한다. --- .../auth/service/AuthTokenProvider.java | 11 ++++++++--- .../solidconnection/auth/service/TokenProvider.java | 6 +++--- .../auth/service/signup/SignUpTokenProvider.java | 7 +++++-- .../solidconnection/auth/token/JwtTokenProvider.java | 10 +++++----- 4 files changed, 21 insertions(+), 13 deletions(-) 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 eee1e405f..3bead1029 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -6,6 +6,7 @@ import com.example.solidconnection.auth.domain.RefreshToken; import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -24,21 +25,25 @@ public class AuthTokenProvider { private final TokenProvider tokenProvider; private final TokenStorage tokenStorage; private final SiteUserRepository siteUserRepository; + private final TokenProperties tokenProperties; public AccessToken generateAccessToken(SiteUser siteUser) { Subject subject = toSubject(siteUser); Role role = siteUser.getRole(); String token = tokenProvider.generateToken( - subject.value(), + subject, Map.of(ROLE_CLAIM_KEY, role.name()), - TokenType.ACCESS + tokenProperties.access().expireTime() ); return new AccessToken(token); } public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { Subject subject = toSubject(siteUser); - String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH); + String token = tokenProvider.generateToken( + subject, + tokenProperties.refresh().expireTime() + ); tokenStorage.saveToken(token, TokenType.REFRESH); return new RefreshToken(token); } diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index 15ccbf34b..7e9b4c6f7 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -1,13 +1,13 @@ package com.example.solidconnection.auth.service; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.Subject; import java.util.Map; public interface TokenProvider { - String generateToken(String string, TokenType tokenType); + String generateToken(Subject subject, long expiration); - String generateToken(String string, Map claims, TokenType tokenType); + String generateToken(Subject subject, Map claims, long expiration); String parseSubject(String token); diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java index 75163e91b..a45290eae 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java @@ -4,9 +4,11 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import com.example.solidconnection.auth.domain.SignUpToken; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.service.TokenStorage; +import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; import java.util.Map; @@ -21,12 +23,13 @@ public class SignUpTokenProvider { private final TokenProvider tokenProvider; private final TokenStorage tokenStorage; + private final TokenProperties tokenProperties; public SignUpToken generateAndSaveSignUpToken(String email, AuthType authType) { String token = tokenProvider.generateToken( - email, + new Subject(email), Map.of(AUTH_TYPE_CLAIM_KEY, authType.toString()), - TokenType.SIGN_UP + tokenProperties.signUp().expireTime() ); tokenStorage.saveToken(token, TokenType.SIGN_UP); return new SignUpToken(token); diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index a43716087..53aeba8a0 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -2,7 +2,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; @@ -21,13 +21,13 @@ public class JwtTokenProvider implements TokenProvider { private final JwtProperties jwtProperties; @Override - public final String generateToken(String string, TokenType tokenType) { - return generateJwtTokenValue(string, Map.of(), tokenType.getExpireTime()); + public String generateToken(Subject subject, long expireTime) { + return generateJwtTokenValue(subject.value(), Map.of(), expireTime); } @Override - public String generateToken(String string, Map customClaims, TokenType tokenType) { - return generateJwtTokenValue(string, customClaims, tokenType.getExpireTime()); + public String generateToken(Subject subject, Map customClaims, long expireTime) { + return generateJwtTokenValue(subject.value(), customClaims, expireTime); } private String generateJwtTokenValue(String subject, Map claims, long expireTime) { From 5b75f998d3f37dc9d8b5af2d7bc7f26d0f49523f Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 02:21:46 +0900 Subject: [PATCH 12/23] =?UTF-8?q?refactor:=20parseSubject=ED=95=A8?= =?UTF-8?q?=EC=88=98=EC=9D=98=20=EB=B0=98=ED=99=98=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=9D=B4=20Subject=EA=B0=80=20=EB=90=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/auth/service/AuthTokenProvider.java | 8 ++++---- .../solidconnection/auth/service/TokenProvider.java | 2 +- .../auth/service/signup/SignUpTokenProvider.java | 2 +- .../solidconnection/auth/token/JwtTokenProvider.java | 4 ++-- .../authentication/TokenAuthenticationProvider.java | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) 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 3bead1029..055930208 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -54,20 +54,20 @@ public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { * - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다. * */ public boolean isValidRefreshToken(String requestedRefreshToken) { - String subject = tokenProvider.parseSubject(requestedRefreshToken); + Subject subject = tokenProvider.parseSubject(requestedRefreshToken); return tokenStorage.findToken(subject, TokenType.REFRESH) .map(foundRefreshToken -> Objects.equals(foundRefreshToken, requestedRefreshToken)) .orElse(false); } public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { - String subject = tokenProvider.parseSubject(accessToken.token()); + Subject subject = tokenProvider.parseSubject(accessToken.token()); tokenStorage.deleteToken(subject, TokenType.REFRESH); } public SiteUser parseSiteUser(String token) { - String subject = tokenProvider.parseSubject(token); - long siteUserId = Long.parseLong(subject); + Subject subject = tokenProvider.parseSubject(token); + long siteUserId = Long.parseLong(subject.value()); return siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); } diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index 7e9b4c6f7..7b40aa088 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -9,7 +9,7 @@ public interface TokenProvider { String generateToken(Subject subject, Map claims, long expiration); - String parseSubject(String token); + Subject parseSubject(String token); T parseClaims(String token, String claimName, Class claimType); } diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java index a45290eae..bf4e77408 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java @@ -60,7 +60,7 @@ private void validateIssuedByServer(String email) { } public String parseEmail(String token) { - return tokenProvider.parseSubject(token); + return tokenProvider.parseSubject(token).value(); } public AuthType parseAuthType(String token) { diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index 53aeba8a0..3e147f0b6 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -44,8 +44,8 @@ private String generateJwtTokenValue(String subject, Map claims, } @Override - public String parseSubject(String token) { - return parseJwtClaims(token).getSubject(); + public Subject parseSubject(String token) { + return new Subject(parseJwtClaims(token).getSubject()); } @Override diff --git a/src/main/java/com/example/solidconnection/security/authentication/TokenAuthenticationProvider.java b/src/main/java/com/example/solidconnection/security/authentication/TokenAuthenticationProvider.java index d0c105884..74783922a 100644 --- a/src/main/java/com/example/solidconnection/security/authentication/TokenAuthenticationProvider.java +++ b/src/main/java/com/example/solidconnection/security/authentication/TokenAuthenticationProvider.java @@ -21,7 +21,7 @@ public Authentication authenticate(Authentication auth) throws AuthenticationExc TokenAuthentication tokenAuth = (TokenAuthentication) auth; String token = tokenAuth.getToken(); - String username = tokenProvider.parseSubject(token); + String username = tokenProvider.parseSubject(token).value(); SiteUserDetails userDetails = (SiteUserDetails) siteUserDetailsService.loadUserByUsername(username); return new TokenAuthentication(token, userDetails); } From d3483865314c183a21a9ee7ce97dfedaf6b1bfbb Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sun, 24 Aug 2025 22:42:43 +0900 Subject: [PATCH 13/23] =?UTF-8?q?refactor:=20TokenStorage=EC=9D=98=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EB=93=A4=EC=9D=B4=20=EA=B0=92=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EB=A5=BC=20=EC=9D=B8=EC=9E=90=EB=A1=9C=20=EA=B0=96?= =?UTF-8?q?=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 값 객체와 제네릭을 최대한 활용하여 코드에 의미를 전달하고 중복 코드를 줄인다. --- .../solidconnection/auth/domain/Token.java | 1 + .../auth/service/AuthTokenProvider.java | 9 +++---- .../auth/service/TokenStorage.java | 9 ++++--- .../service/signup/SignUpTokenProvider.java | 12 ++++----- .../auth/token/RedisTokenStorage.java | 27 +++++++++---------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/domain/Token.java b/src/main/java/com/example/solidconnection/auth/domain/Token.java index d10f4cddf..3613bffb0 100644 --- a/src/main/java/com/example/solidconnection/auth/domain/Token.java +++ b/src/main/java/com/example/solidconnection/auth/domain/Token.java @@ -2,4 +2,5 @@ public interface Token { + String 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 055930208..a87128a5c 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -5,7 +5,6 @@ import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.domain.RefreshToken; import com.example.solidconnection.auth.domain.Subject; -import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.Role; @@ -44,8 +43,8 @@ public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { subject, tokenProperties.refresh().expireTime() ); - tokenStorage.saveToken(token, TokenType.REFRESH); - return new RefreshToken(token); + RefreshToken refreshToken = new RefreshToken(token); + return tokenStorage.saveToken(subject, refreshToken); } /* @@ -55,14 +54,14 @@ public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { * */ public boolean isValidRefreshToken(String requestedRefreshToken) { Subject subject = tokenProvider.parseSubject(requestedRefreshToken); - return tokenStorage.findToken(subject, TokenType.REFRESH) + return tokenStorage.findToken(subject, RefreshToken.class) .map(foundRefreshToken -> Objects.equals(foundRefreshToken, requestedRefreshToken)) .orElse(false); } public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { Subject subject = tokenProvider.parseSubject(accessToken.token()); - tokenStorage.deleteToken(subject, TokenType.REFRESH); + tokenStorage.deleteToken(subject, RefreshToken.class); } public SiteUser parseSiteUser(String token) { diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenStorage.java b/src/main/java/com/example/solidconnection/auth/service/TokenStorage.java index 0f27756a5..19c3311f1 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenStorage.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenStorage.java @@ -1,13 +1,14 @@ package com.example.solidconnection.auth.service; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.Subject; +import com.example.solidconnection.auth.domain.Token; import java.util.Optional; public interface TokenStorage { - String saveToken(String token, TokenType tokenType); + T saveToken(Subject subject, T token); - Optional findToken(String subject, TokenType tokenType); + Optional findToken(Subject subject, Class tokenClass); - void deleteToken(String subject, TokenType tokenType); + void deleteToken(Subject subject, Class tokenClass); } diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java index bf4e77408..a1eaaf3e5 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java @@ -5,7 +5,6 @@ import com.example.solidconnection.auth.domain.SignUpToken; import com.example.solidconnection.auth.domain.Subject; -import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.service.TokenStorage; import com.example.solidconnection.auth.token.config.TokenProperties; @@ -26,17 +25,18 @@ public class SignUpTokenProvider { private final TokenProperties tokenProperties; public SignUpToken generateAndSaveSignUpToken(String email, AuthType authType) { + Subject subject = new Subject(email); String token = tokenProvider.generateToken( - new Subject(email), + subject, Map.of(AUTH_TYPE_CLAIM_KEY, authType.toString()), tokenProperties.signUp().expireTime() ); - tokenStorage.saveToken(token, TokenType.SIGN_UP); - return new SignUpToken(token); + SignUpToken signUpToken = new SignUpToken(token); + return tokenStorage.saveToken(subject, signUpToken); } public void deleteByEmail(String email) { - tokenStorage.deleteToken(email, TokenType.SIGN_UP); + tokenStorage.deleteToken(new Subject(email), SignUpToken.class); } public void validateSignUpToken(String token) { @@ -55,7 +55,7 @@ private void validateFormatAndExpiration(String token) { // 파싱되는지, Aut } private void validateIssuedByServer(String email) { - tokenStorage.findToken(email, TokenType.SIGN_UP) + tokenStorage.findToken(new Subject(email), SignUpToken.class) .orElseThrow(() -> new CustomException(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER)); } diff --git a/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java b/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java index 988336d0c..aeb974417 100644 --- a/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java +++ b/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java @@ -1,8 +1,9 @@ package com.example.solidconnection.auth.token; -import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.domain.Subject; +import com.example.solidconnection.auth.domain.Token; import com.example.solidconnection.auth.service.TokenStorage; +import com.example.solidconnection.auth.token.config.TokenProperties; import java.util.Optional; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; @@ -13,24 +14,22 @@ @RequiredArgsConstructor public class RedisTokenStorage implements TokenStorage { - private final TokenProvider tokenProvider; private final RedisTemplate redisTemplate; @Override - public String saveToken(String token, TokenType tokenType) { - String subject = tokenProvider.parseSubject(token); + public T saveToken(Subject subject, T token) { redisTemplate.opsForValue().set( - createKey(subject, tokenType), - token, - tokenType.getExpireTime(), + createKey(subject, token.getClass()), + token.token(), + TokenProperties.getExpireTime(token.getClass()), TimeUnit.MILLISECONDS ); return token; } @Override - public Optional findToken(String subject, TokenType tokenType) { - String key = createKey(subject, tokenType); + public Optional findToken(Subject subject, Class tokenClass) { + String key = createKey(subject, tokenClass); String foundTokenValue = redisTemplate.opsForValue().get(key); if (foundTokenValue == null || foundTokenValue.isBlank()) { return Optional.empty(); @@ -39,12 +38,12 @@ public Optional findToken(String subject, TokenType tokenType) { } @Override - public void deleteToken(String subject, TokenType tokenType) { - String key = createKey(subject, tokenType); + public void deleteToken(Subject subject, Class tokenClass) { + String key = createKey(subject, tokenClass); redisTemplate.delete(key); } - private String createKey(String subject, TokenType tokenType) { - return tokenType.addPrefix(subject); + private String createKey(Subject subject, Class tokenClass) { + return TokenProperties.getStorageKeyPrefix(tokenClass) + ":" + subject.value(); } } From bad0187811b3e9032d1394ba30f5832fcb40cc19 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 00:18:36 +0900 Subject: [PATCH 14/23] =?UTF-8?q?test:=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B7=B8=EB=8B=88=EC=B2=98=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EC=97=90=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/AuthServiceTest.java | 9 ++-- .../auth/service/AuthTokenProviderTest.java | 14 +++---- .../auth/service/JwtTokenProviderTest.java | 36 ++++++++-------- .../service/signin/SignInServiceTest.java | 13 +++--- .../signup/SignUpTokenProviderTest.java | 16 +++---- .../auth/token/RedisTokenStorageTest.java | 42 +++++++++++-------- 6 files changed, 70 insertions(+), 60 deletions(-) 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 caf016422..98454da0f 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -7,7 +7,7 @@ import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.domain.RefreshToken; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; @@ -49,7 +49,7 @@ class AuthServiceTest { private SiteUser siteUser; private AccessToken accessToken; - private String expectedSubject; + private Subject expectedSubject; @BeforeEach void setUp() { @@ -65,7 +65,7 @@ void setUp() { // then assertAll( - () -> assertThat(tokenStorage.findToken(expectedSubject, TokenType.REFRESH)).isEmpty(), + () -> assertThat(tokenStorage.findToken(expectedSubject, RefreshToken.class)).isEmpty(), () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } @@ -77,11 +77,10 @@ void setUp() { // then LocalDate tomorrow = LocalDate.now().plusDays(1); - String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); SiteUser actualSitUser = siteUserRepository.findById(siteUser.getId()).orElseThrow(); assertAll( () -> assertThat(actualSitUser.getQuitedAt()).isEqualTo(tomorrow), - () -> assertThat(tokenStorage.findToken(expectedSubject, TokenType.REFRESH)).isEmpty(), + () -> assertThat(tokenStorage.findToken(expectedSubject, RefreshToken.class)).isEmpty(), () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } 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 849940abb..678251154 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -5,7 +5,7 @@ import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.domain.RefreshToken; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -34,12 +34,12 @@ class AuthTokenProviderTest { private SiteUserFixture siteUserFixture; private SiteUser siteUser; - private String expectedSubject; + private Subject expectedSubject; @BeforeEach void setUp() { siteUser = siteUserFixture.사용자(); - expectedSubject = siteUser.getId().toString(); + expectedSubject = new Subject(siteUser.getId().toString()); } @Test @@ -49,7 +49,7 @@ void setUp() { // then String accessTokenValue = accessToken.token(); - String actualSubject = tokenProvider.parseSubject(accessTokenValue); + Subject actualSubject = tokenProvider.parseSubject(accessTokenValue); Role actualRole = authTokenProvider.parseSiteUser(accessTokenValue).getRole(); assertAll( () -> assertThat(accessTokenValue).isNotNull(), @@ -67,8 +67,8 @@ class 리프레시_토큰을_제공한다 { RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); // then - String actualSubject = tokenProvider.parseSubject(refreshToken.token()); - Optional savedRefreshToken = tokenStorage.findToken(expectedSubject, TokenType.REFRESH); + Subject actualSubject = tokenProvider.parseSubject(refreshToken.token()); + Optional savedRefreshToken = tokenStorage.findToken(expectedSubject, RefreshToken.class); assertAll( () -> assertThat(savedRefreshToken).hasValue(refreshToken.token()), () -> assertThat(actualSubject).isEqualTo(expectedSubject) @@ -98,7 +98,7 @@ class 리프레시_토큰을_제공한다 { authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); // then - assertThat(tokenStorage.findToken(expectedSubject, TokenType.REFRESH)).isEmpty(); + assertThat(tokenStorage.findToken(expectedSubject, RefreshToken.class)).isEmpty(); } } diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index 591802b27..4e05161a2 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -4,7 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.token.JwtTokenProvider; import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; @@ -37,41 +37,41 @@ class 토큰을_생성한다 { @Test void subject_만_있는_토큰을_생성한다() { // given - String expectedSubject = "subject123"; - TokenType expectedTokenType = TokenType.ACCESS; + Subject expectedSubject = new Subject("subject123"); + long expectedExpireTime = 10000L; // when - String token = tokenProvider.generateToken(expectedSubject, expectedTokenType); + String token = tokenProvider.generateToken(expectedSubject, expectedExpireTime); // then - subject와 만료 시간이 일치하는지 검증 - String actualSubject = tokenProvider.parseSubject(token); + Subject actualSubject = tokenProvider.parseSubject(token); long actualExpireTime = getActualExpireTime(token); assertAll( () -> assertThat(actualSubject).isEqualTo(expectedSubject), - () -> assertThat(actualExpireTime).isEqualTo(expectedTokenType.getExpireTime()) + () -> assertThat(actualExpireTime).isEqualTo(expectedExpireTime) ); } @Test void subject_와_claims_가_있는_토큰을_생성한다() { // given - String expectedSubject = "subject123"; + Subject expectedSubject = new Subject("subject123"); String key1 = "key1"; String value1 = "value1"; String key2 = "key2"; String value2 = "value2"; Map customClaims = Map.of(key1, value1, key2, value2); - TokenType expectedTokenType = TokenType.ACCESS; + long expectedExpireTime = 10000L; // when - String token = tokenProvider.generateToken(expectedSubject, customClaims, expectedTokenType); + String token = tokenProvider.generateToken(expectedSubject, customClaims, expectedExpireTime); // then - subject와 커스텀 클레임이 일치하는지 검증 - String actualSubject = tokenProvider.parseSubject(token); + Subject actualSubject = tokenProvider.parseSubject(token); long actualExpireTime = getActualExpireTime(token); assertAll( () -> assertThat(actualSubject).isEqualTo(expectedSubject), - () -> assertThat(actualExpireTime).isEqualTo(expectedTokenType.getExpireTime()), + () -> assertThat(actualExpireTime).isEqualTo(expectedExpireTime), () -> assertThat(tokenProvider.parseClaims(token, key1, String.class)).isEqualTo(value1), () -> assertThat(tokenProvider.parseClaims(token, key2, String.class)).isEqualTo(value2) ); @@ -92,11 +92,12 @@ class 토큰으로부터_subject_를_추출한다 { @Test void 유효한_토큰의_subject_를_추출한다() { // given - String subject = "subject000"; - String token = tokenProvider.generateToken(subject, TokenType.SIGN_UP); + Subject subject = new Subject("subject000"); + long expireTime = 10000L; + String token = tokenProvider.generateToken(subject, expireTime); // when - String extractedSubject = tokenProvider.parseSubject(token); + Subject extractedSubject = tokenProvider.parseSubject(token); // then assertThat(extractedSubject).isEqualTo(subject); @@ -118,14 +119,15 @@ class 토큰으로부터_subject_를_추출한다 { @Nested class 토큰으로부터_claim_을_추출한다 { - private final String subject = "subject"; + private final Subject subject = new Subject("subject"); private final String claimKey = "key"; private final String claimValue = "value"; + private final long expireTime = 10000L; @Test void 유효한_토큰의_claim_을_추출한다() { // given - String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), TokenType.SIGN_UP); + String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), expireTime); // when String actualClaimValue = tokenProvider.parseClaims(token, claimKey, String.class); @@ -149,7 +151,7 @@ class 토큰으로부터_claim_을_추출한다 { @Test void 존재하지_않는_claim_을_추출하면_null을_반환한다() { // given - String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), TokenType.SIGN_UP); + String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), expireTime); String nonExistentClaimKey = "nonExistentKey"; // when diff --git a/src/test/java/com/example/solidconnection/auth/service/signin/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/signin/SignInServiceTest.java index aa0d07307..957c5c3a1 100644 --- a/src/test/java/com/example/solidconnection/auth/service/signin/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/signin/SignInServiceTest.java @@ -3,7 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.RefreshToken; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.service.TokenStorage; @@ -34,12 +35,12 @@ class SignInServiceTest { private SiteUserFixture siteUserFixture; private SiteUser user; - private String subject; + private Subject subject; @BeforeEach void setUp() { user = siteUserFixture.사용자(); - subject = user.getId().toString(); + subject = new Subject(user.getId().toString()); } @Test @@ -48,9 +49,9 @@ void setUp() { SignInResponse signInResponse = signInService.signIn(user); // then - String accessTokenSubject = tokenProvider.parseSubject(signInResponse.accessToken()); - String refreshTokenSubject = tokenProvider.parseSubject(signInResponse.refreshToken()); - Optional savedRefreshToken = tokenStorage.findToken(subject, TokenType.REFRESH); + Subject accessTokenSubject = tokenProvider.parseSubject(signInResponse.accessToken()); + Subject refreshTokenSubject = tokenProvider.parseSubject(signInResponse.refreshToken()); + Optional savedRefreshToken = tokenStorage.findToken(subject, RefreshToken.class); assertAll( () -> assertThat(accessTokenSubject).isEqualTo(subject), () -> assertThat(refreshTokenSubject).isEqualTo(subject), diff --git a/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java index d38f562be..1e7f03e18 100644 --- a/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java @@ -8,7 +8,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.given; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.SignUpToken; +import com.example.solidconnection.auth.domain.Subject; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.service.TokenStorage; import com.example.solidconnection.auth.token.config.JwtProperties; @@ -45,6 +46,7 @@ class SignUpTokenProviderTest { private final String authTypeClaimKey = "authType"; private final String email = "test@email.com"; + private final Subject subject = new Subject(email); private final AuthType authType = AuthType.KAKAO; @Test @@ -53,11 +55,11 @@ class SignUpTokenProviderTest { String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType).token(); // then - String actualSubject = tokenProvider.parseSubject(signUpToken); + Subject actualSubject = tokenProvider.parseSubject(signUpToken); String actualAuthType = tokenProvider.parseClaims(signUpToken, authTypeClaimKey, String.class); - Optional actualSavedToken = tokenStorage.findToken(email, TokenType.SIGN_UP); + Optional actualSavedToken = tokenStorage.findToken(actualSubject, SignUpToken.class); assertAll( - () -> assertThat(actualSubject).isEqualTo(email), + () -> assertThat(actualSubject.value()).isEqualTo(email), () -> assertThat(actualAuthType).isEqualTo(authType.toString()), () -> assertThat(actualSavedToken).hasValue(signUpToken) ); @@ -72,7 +74,7 @@ class SignUpTokenProviderTest { signUpTokenProvider.deleteByEmail(email); // then - assertThat(tokenStorage.findToken(email, TokenType.SIGN_UP)).isEmpty(); + assertThat(tokenStorage.findToken(subject, SignUpToken.class)).isEmpty(); } @Nested @@ -114,7 +116,7 @@ class 주어진_회원가입_토큰을_검증한다 { // given String wrongAuthType = "카카오"; Map wrongClaim = new HashMap<>(Map.of(authTypeClaimKey, wrongAuthType)); - String wrongAuthTypeClaim = tokenProvider.generateToken(email, wrongClaim, TokenType.SIGN_UP); + String wrongAuthTypeClaim = tokenProvider.generateToken(subject, wrongClaim, 10000L); // when & then assertThatCode(() -> signUpTokenProvider.validateSignUpToken(wrongAuthTypeClaim)) @@ -126,7 +128,7 @@ class 주어진_회원가입_토큰을_검증한다 { void 우리_서버에_발급된_토큰이_아니면_예외가_발생한다() { // given String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType).token(); - given(tokenStorage.findToken(email, TokenType.SIGN_UP)).willReturn(empty()); + given(tokenStorage.findToken(subject, SignUpToken.class)).willReturn(empty()); // when & then assertThatCode(() -> signUpTokenProvider.validateSignUpToken(signUpToken)) diff --git a/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java b/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java index 3a9a42858..9aa03ac61 100644 --- a/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java +++ b/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java @@ -3,10 +3,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.domain.Subject; +import com.example.solidconnection.auth.domain.Token; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -22,21 +25,26 @@ class RedisTokenStorageTest { @Autowired private TokenProvider tokenProvider; - private final String subject = "subject123"; - private final TokenType tokenType = TokenType.ACCESS; + private Subject subject; + private Token expectedToken; + private Class tokenClass; + + @BeforeEach + void setUp() { + subject = new Subject("subject123"); + expectedToken = new AccessToken(tokenProvider.generateToken(subject, 100000L)); + tokenClass = expectedToken.getClass(); + } @Test void 토큰을_저장한다() { - // given - String expectedToken = tokenProvider.generateToken(subject, tokenType); - // when - String savedToken = redisTokenStorage.saveToken(expectedToken, tokenType); + Token savedToken = redisTokenStorage.saveToken(subject, expectedToken); // then - Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + Optional foundToken = redisTokenStorage.findToken(subject, tokenClass); assertAll( - () -> assertThat(foundToken).hasValue(expectedToken), + () -> assertThat(foundToken).hasValue(expectedToken.token()), () -> assertThat(savedToken).isEqualTo(expectedToken) ); } @@ -47,20 +55,19 @@ class 토큰을_조회한다 { @Test void 저장된_토큰이_있으면_Optional에_담아_반한다() { // given - String expectedToken = tokenProvider.generateToken(subject, tokenType); - redisTokenStorage.saveToken(expectedToken, tokenType); + redisTokenStorage.saveToken(subject, expectedToken); // when - Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + Optional actualToken = redisTokenStorage.findToken(subject, tokenClass); // then - assertThat(foundToken).hasValue(expectedToken); + assertThat(actualToken).hasValue(expectedToken.token()); } @Test void 저장된_토큰이_없으면_빈_Optional을_반환한다() { // when - Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + Optional foundToken = redisTokenStorage.findToken(subject, tokenClass); // then assertThat(foundToken).isEmpty(); @@ -70,14 +77,13 @@ class 토큰을_조회한다 { @Test void 토큰을_삭제한다() { // given - String expectedToken = tokenProvider.generateToken(subject, tokenType); - redisTokenStorage.saveToken(expectedToken, tokenType); + redisTokenStorage.saveToken(subject, expectedToken); // when - redisTokenStorage.deleteToken(subject, tokenType); + redisTokenStorage.deleteToken(subject, tokenClass); // then - Optional foundToken = redisTokenStorage.findToken(subject, tokenType); + Optional foundToken = redisTokenStorage.findToken(subject, tokenClass); assertThat(foundToken).isEmpty(); } } From a5e05ca6397b47759d278a906c7cf0a168265cd8 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 00:22:10 +0900 Subject: [PATCH 15/23] =?UTF-8?q?test:=20=EB=B3=80=EC=88=98=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=20=EC=A4=91=EB=B3=B5=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/JwtTokenProviderTest.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index 4e05161a2..20d8c5460 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -31,15 +31,14 @@ class JwtTokenProviderTest { @Autowired private JwtProperties jwtProperties; + private final Subject expectedSubject = new Subject("subject123"); + private final long expectedExpireTime = 10000L; + @Nested class 토큰을_생성한다 { @Test void subject_만_있는_토큰을_생성한다() { - // given - Subject expectedSubject = new Subject("subject123"); - long expectedExpireTime = 10000L; - // when String token = tokenProvider.generateToken(expectedSubject, expectedExpireTime); @@ -55,13 +54,11 @@ class 토큰을_생성한다 { @Test void subject_와_claims_가_있는_토큰을_생성한다() { // given - Subject expectedSubject = new Subject("subject123"); String key1 = "key1"; String value1 = "value1"; String key2 = "key2"; String value2 = "value2"; Map customClaims = Map.of(key1, value1, key2, value2); - long expectedExpireTime = 10000L; // when String token = tokenProvider.generateToken(expectedSubject, customClaims, expectedExpireTime); @@ -92,15 +89,13 @@ class 토큰으로부터_subject_를_추출한다 { @Test void 유효한_토큰의_subject_를_추출한다() { // given - Subject subject = new Subject("subject000"); - long expireTime = 10000L; - String token = tokenProvider.generateToken(subject, expireTime); + String token = tokenProvider.generateToken(expectedSubject, expectedExpireTime); // when - Subject extractedSubject = tokenProvider.parseSubject(token); + Subject actualSubject = tokenProvider.parseSubject(token); // then - assertThat(extractedSubject).isEqualTo(subject); + assertThat(actualSubject).isEqualTo(expectedSubject); } @Test @@ -119,15 +114,17 @@ class 토큰으로부터_subject_를_추출한다 { @Nested class 토큰으로부터_claim_을_추출한다 { - private final Subject subject = new Subject("subject"); private final String claimKey = "key"; private final String claimValue = "value"; - private final long expireTime = 10000L; @Test void 유효한_토큰의_claim_을_추출한다() { // given - String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), expireTime); + String token = tokenProvider.generateToken( + expectedSubject, + Map.of(claimKey, claimValue), + expectedExpireTime + ); // when String actualClaimValue = tokenProvider.parseClaims(token, claimKey, String.class); @@ -151,7 +148,11 @@ class 토큰으로부터_claim_을_추출한다 { @Test void 존재하지_않는_claim_을_추출하면_null을_반환한다() { // given - String token = tokenProvider.generateToken(subject, Map.of(claimKey, claimValue), expireTime); + String token = tokenProvider.generateToken( + expectedSubject, + Map.of(claimKey, claimValue), + expectedExpireTime + ); String nonExistentClaimKey = "nonExistentKey"; // when From bb371a44116e65f34847b911ac8ec4b8f608676f Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 00:48:44 +0900 Subject: [PATCH 16/23] =?UTF-8?q?refactor:=20TokenValue=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EA=B3=A0,=20To?= =?UTF-8?q?kenProperties=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RefreshTokenCookieManager.java | 8 +++--- .../auth/domain/TokenType.java | 25 ------------------- .../signup/PasswordTemporaryStorage.java | 6 +++-- .../auth/token/TokenBlackListService.java | 14 +++++++---- .../RefreshTokenCookieManagerTest.java | 7 ++++-- .../service/TokenBlackListServiceTest.java | 7 ++++-- .../filter/SignOutCheckFilterTest.java | 7 ++++-- 7 files changed, 33 insertions(+), 41 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/auth/domain/TokenType.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java index 7c6f4ec04..c036bdb81 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java +++ b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java @@ -3,7 +3,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_NOT_EXISTS; import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.common.exception.CustomException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -23,10 +23,12 @@ public class RefreshTokenCookieManager { private static final String PATH = "/"; private final RefreshTokenCookieProperties properties; + private final TokenProperties tokenProperties; public void setCookie(HttpServletResponse response, String refreshToken) { - long maxAge = convertExpireTimeToCookieMaxAge(TokenType.REFRESH.getExpireTime()); - setRefreshTokenCookie(response, refreshToken, maxAge); + long tokenExpireTime = tokenProperties.refresh().expireTime(); + long cookieMaxAge = convertExpireTimeToCookieMaxAge(tokenExpireTime); + setRefreshTokenCookie(response, refreshToken, cookieMaxAge); } private long convertExpireTimeToCookieMaxAge(long milliSeconds) { diff --git a/src/main/java/com/example/solidconnection/auth/domain/TokenType.java b/src/main/java/com/example/solidconnection/auth/domain/TokenType.java deleted file mode 100644 index 560b0e139..000000000 --- a/src/main/java/com/example/solidconnection/auth/domain/TokenType.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.solidconnection.auth.domain; - -import lombok.Getter; - -@Getter -public enum TokenType { - - ACCESS("ACCESS:", 1000L * 60 * 60), // 1hour - REFRESH("REFRESH:", 1000L * 60 * 60 * 24 * 90), // 90days - BLACKLIST("BLACKLIST:", ACCESS.expireTime), - SIGN_UP("SIGN_UP:", 1000L * 60 * 10), // 10min - ; - - private final String prefix; - private final long expireTime; - - TokenType(String prefix, long expireTime) { - this.prefix = prefix; - this.expireTime = expireTime; - } - - public String addPrefix(String string) { - return prefix + string; - } -} diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java b/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java index 46d9db157..5397f1215 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java @@ -1,6 +1,6 @@ package com.example.solidconnection.auth.service.signup; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.token.config.TokenProperties; import java.util.Optional; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; @@ -16,13 +16,15 @@ public class PasswordTemporaryStorage { private final RedisTemplate redisTemplate; private final PasswordEncoder passwordEncoder; + private final TokenProperties tokenProperties; public void save(String email, String rawPassword) { String encodedPassword = passwordEncoder.encode(rawPassword); + long expireTime = tokenProperties.signUp().expireTime(); redisTemplate.opsForValue().set( convertToKey(email), encodedPassword, - TokenType.SIGN_UP.getExpireTime(), + expireTime, TimeUnit.MILLISECONDS ); } diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java index cf50590a8..16fcfc44e 100644 --- a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -1,8 +1,7 @@ package com.example.solidconnection.auth.token; -import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; - import com.example.solidconnection.auth.domain.AccessToken; +import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.security.filter.BlacklistChecker; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; @@ -14,6 +13,7 @@ public class TokenBlackListService implements BlacklistChecker { private static final String SIGN_OUT_VALUE = "signOut"; + private final TokenProperties tokenProperties; private final RedisTemplate redisTemplate; /* @@ -22,13 +22,17 @@ public class TokenBlackListService implements BlacklistChecker { * - value = {SIGN_OUT_VALUE} -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. * */ public void addToBlacklist(AccessToken accessToken) { - String blackListKey = BLACKLIST.addPrefix(accessToken.token()); + String blackListKey = createKey(accessToken.token()); redisTemplate.opsForValue().set(blackListKey, SIGN_OUT_VALUE); } @Override public boolean isTokenBlacklisted(String accessToken) { - String blackListTokenKey = BLACKLIST.addPrefix(accessToken); - return redisTemplate.hasKey(blackListTokenKey); + String blackListKey = createKey(accessToken); + return redisTemplate.hasKey(blackListKey); + } + + private String createKey(String accessToken) { + return tokenProperties.blackList().storageKeyPrefix() + ":" + accessToken; } } diff --git a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java index a5924b860..0df140756 100644 --- a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java +++ b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java @@ -6,7 +6,7 @@ import static org.mockito.BDDMockito.given; import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties; -import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -32,6 +32,9 @@ class RefreshTokenCookieManagerTest { @Autowired private RefreshTokenCookieManager cookieManager; + @Autowired + private TokenProperties tokenProperties; + @MockBean private RefreshTokenCookieProperties refreshTokenCookieProperties; @@ -59,7 +62,7 @@ void setUp() { () -> assertThat(header).contains("HttpOnly"), () -> assertThat(header).contains("Secure"), () -> assertThat(header).contains("Path=/"), - () -> assertThat(header).contains("Max-Age=" + TokenType.REFRESH.getExpireTime() / 1000), + () -> assertThat(header).contains("Max-Age=" + tokenProperties.refresh().expireTime() / 1000), () -> assertThat(header).contains("Domain=" + domain), () -> assertThat(header).contains("SameSite=" + SameSite.LAX.attributeValue()) ); diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java index dbe6f5acc..7d27a2fcb 100644 --- a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -1,10 +1,10 @@ package com.example.solidconnection.auth.service; -import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; import static org.assertj.core.api.Assertions.assertThat; import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.token.TokenBlackListService; +import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -23,6 +23,9 @@ class TokenBlackListServiceTest { @Autowired private RedisTemplate redisTemplate; + @Autowired + private TokenProperties tokenProperties; + private AccessToken accessToken; @BeforeEach @@ -36,7 +39,7 @@ void setUp() { tokenBlackListService.addToBlacklist(accessToken); // then - String blackListTokenKey = BLACKLIST.addPrefix(accessToken.token()); + String blackListTokenKey = tokenProperties.blackList().storageKeyPrefix() + ":" + accessToken; String foundBlackListToken = redisTemplate.opsForValue().get(blackListTokenKey); assertThat(foundBlackListToken).isNotNull(); } diff --git a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java index 3d78f1307..2667671cd 100644 --- a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java @@ -1,12 +1,12 @@ package com.example.solidconnection.security.filter; -import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.spy; import com.example.solidconnection.auth.token.config.JwtProperties; +import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; @@ -37,6 +37,9 @@ class SignOutCheckFilterTest { @Autowired private JwtProperties jwtProperties; + @Autowired + private TokenProperties tokenProperties; + private HttpServletRequest request; private HttpServletResponse response; private FilterChain filterChain; @@ -58,7 +61,7 @@ void setUp() { // given String token = createToken(subject); request = createRequest(token); - String refreshTokenKey = BLACKLIST.addPrefix(token); + String refreshTokenKey = tokenProperties.blackList().storageKeyPrefix() + ":" + token; redisTemplate.opsForValue().set(refreshTokenKey, token); // when & then From cef0864ac6e64305fd7da2fea4b171a66e3258ce Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 00:54:26 +0900 Subject: [PATCH 17/23] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=8B=9C,=20=EC=95=A1=EC=84=B8=EC=8A=A4=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EC=9D=84=20=EC=83=88=EB=A1=9C=20=EB=A7=8C?= =?UTF-8?q?=EB=93=9C=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/auth/service/AuthService.java | 6 ++---- .../solidconnection/auth/service/AuthTokenProvider.java | 4 ++-- .../auth/token/TokenBlackListService.java | 5 ++--- .../auth/service/AuthTokenProviderTest.java | 2 +- .../auth/service/TokenBlackListServiceTest.java | 9 ++++----- 5 files changed, 11 insertions(+), 15 deletions(-) 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 0aeafe4ad..1c9478e80 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -27,11 +27,9 @@ public class AuthService { * - 엑세스 토큰을 블랙리스트에 추가한다. * - 리프레시 토큰을 삭제한다. * */ - public void signOut(String token) { - SiteUser siteUser = authTokenProvider.parseSiteUser(token); - AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser); - authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); + public void signOut(String accessToken) { tokenBlackListService.addToBlacklist(accessToken); + authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); } /* 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 a87128a5c..ef188833b 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -59,8 +59,8 @@ public boolean isValidRefreshToken(String requestedRefreshToken) { .orElse(false); } - public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { - Subject subject = tokenProvider.parseSubject(accessToken.token()); + public void deleteRefreshTokenByAccessToken(String accessToken) { + Subject subject = tokenProvider.parseSubject(accessToken); tokenStorage.deleteToken(subject, RefreshToken.class); } diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java index 16fcfc44e..9777ec0c3 100644 --- a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -1,6 +1,5 @@ package com.example.solidconnection.auth.token; -import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.security.filter.BlacklistChecker; import lombok.RequiredArgsConstructor; @@ -21,8 +20,8 @@ public class TokenBlackListService implements BlacklistChecker { * - key = BLACKLIST:{accessToken} * - value = {SIGN_OUT_VALUE} -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. * */ - public void addToBlacklist(AccessToken accessToken) { - String blackListKey = createKey(accessToken.token()); + public void addToBlacklist(String accessToken) { + String blackListKey = createKey(accessToken); redisTemplate.opsForValue().set(blackListKey, SIGN_OUT_VALUE); } 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 678251154..54e5f236c 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -95,7 +95,7 @@ class 리프레시_토큰을_제공한다 { AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser); // when - authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); + authTokenProvider.deleteRefreshTokenByAccessToken(accessToken.token()); // then assertThat(tokenStorage.findToken(expectedSubject, RefreshToken.class)).isEmpty(); diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java index 7d27a2fcb..74d6a9b7f 100644 --- a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.example.solidconnection.auth.domain.AccessToken; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -26,11 +25,11 @@ class TokenBlackListServiceTest { @Autowired private TokenProperties tokenProperties; - private AccessToken accessToken; + private String accessToken; @BeforeEach void setUp() { - accessToken = new AccessToken("tokenValue"); + accessToken = "accessToken"; } @Test @@ -53,13 +52,13 @@ class 블랙리스트에_있는_토큰인지_확인한다 { tokenBlackListService.addToBlacklist(accessToken); // when, then - assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue(); + assertThat(tokenBlackListService.isTokenBlacklisted(accessToken)).isTrue(); } @Test void 블랙리스트에_토큰이_없는_경우() { // when, then - assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isFalse(); + assertThat(tokenBlackListService.isTokenBlacklisted(accessToken)).isFalse(); } } } From a75f1442784394af1fca9999785a5f60d4c74ce8 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 00:59:33 +0900 Subject: [PATCH 18/23] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/auth/client/AppleOAuthClient.java | 5 +++-- .../solidconnection/auth/client/KakaoOAuthClient.java | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java index 48009cc82..9b17214ee 100644 --- a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java @@ -25,8 +25,9 @@ import org.springframework.web.client.RestTemplate; /* - * 애플 인증을 위한 OAuth2 클라이언트 - * https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens + * - 애플 인증을 위한 OAuth2 클라이언트 + * - https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens + * - OAuthClient 인터페이스를 사용하는 전략 패턴으로 구현됨 * */ @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java index a25743f7d..b65e70417 100644 --- a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java @@ -23,10 +23,11 @@ import org.springframework.web.util.UriComponentsBuilder; /* - * 카카오 인증을 위한 OAuth2 클라이언트 - * https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-code - * https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token - * https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info + * - 카카오 인증을 위한 OAuth2 클라이언트 + * - https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-code + * - https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token + * - https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info + * - OAuthClient 인터페이스를 사용하는 전략 패턴으로 구현됨 * */ @Component @RequiredArgsConstructor From baecd6a4bf5bbd1c7c66b387a5c6c69ebcfe25f9 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 22:15:23 +0900 Subject: [PATCH 19/23] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=86=A0=ED=81=B0=20=EA=B2=80=EC=A6=9D=20=EC=8B=9C?= =?UTF-8?q?,=20=EC=84=9C=EB=B2=84=EC=97=90=20=EC=A0=80=EC=9E=A5=EB=90=9C?= =?UTF-8?q?=20=EA=B0=92=EA=B3=BC=20=EB=8F=99=EC=9D=BC=ED=95=9C=EC=A7=80=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - code rabbit 리뷰 반영: https://github.com/solid-connection/solid-connect-server/pull/479#discussion_r2296724578 --- .../solidconnection/auth/controller/AuthController.java | 2 +- .../auth/service/signup/SignUpTokenProvider.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) 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 6ac7a3224..9fbd2f225 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -10,10 +10,10 @@ import com.example.solidconnection.auth.dto.oauth.OAuthResponse; import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse; import com.example.solidconnection.auth.service.AuthService; +import com.example.solidconnection.auth.service.oauth.OAuthService; import com.example.solidconnection.auth.service.signin.EmailSignInService; import com.example.solidconnection.auth.service.signup.EmailSignUpTokenProvider; import com.example.solidconnection.auth.service.signup.SignUpService; -import com.example.solidconnection.auth.service.oauth.OAuthService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.common.resolver.AuthorizedUser; diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java index a1eaaf3e5..b9f29cc87 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java @@ -41,8 +41,7 @@ public void deleteByEmail(String email) { public void validateSignUpToken(String token) { validateFormatAndExpiration(token); - String email = parseEmail(token); - validateIssuedByServer(email); + validateIssuedByServer(token); } private void validateFormatAndExpiration(String token) { // 파싱되는지, AuthType이 포함되어있는지 검증 @@ -54,8 +53,10 @@ private void validateFormatAndExpiration(String token) { // 파싱되는지, Aut } } - private void validateIssuedByServer(String email) { + private void validateIssuedByServer(String token) { + String email = parseEmail(token); tokenStorage.findToken(new Subject(email), SignUpToken.class) + .filter(foundToken -> foundToken.equals(token)) .orElseThrow(() -> new CustomException(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER)); } From 7a2acdff9ec499ffe382bc72ac534b32c508a033 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 22:17:54 +0900 Subject: [PATCH 20/23] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=86=A0=ED=81=B0=20=EA=B2=80=EC=A6=9D=20=EC=8B=9C?= =?UTF-8?q?,=20subject=20=EC=A1=B4=EC=9E=AC=20=EA=B2=80=EC=A6=9D=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 - code rabbit 리뷰 반영: https://github.com/solid-connection/solid-connect-server/pull/479#discussion_r2296724579 --- .../auth/service/signup/SignUpTokenProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java index b9f29cc87..f108365f3 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/SignUpTokenProvider.java @@ -44,8 +44,9 @@ public void validateSignUpToken(String token) { validateIssuedByServer(token); } - private void validateFormatAndExpiration(String token) { // 파싱되는지, AuthType이 포함되어있는지 검증 + private void validateFormatAndExpiration(String token) { // subject와 claims가 파싱되는지, AuthType이 포함되어있는지 검증 try { + tokenProvider.parseSubject(token); String serializedAuthType = tokenProvider.parseClaims(token, AUTH_TYPE_CLAIM_KEY, String.class); AuthType.valueOf(serializedAuthType); } catch (Exception e) { From b085289e478ed1c12d1aecc0b2e5d0af0af209cd Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 22:23:58 +0900 Subject: [PATCH 21/23] =?UTF-8?q?refactor:=20subject=20=EC=B6=94=EC=B6=9C?= =?UTF-8?q?=20=EC=8B=9C,=20subject=EA=B0=80=20=EC=97=86=EC=9C=BC=EB=A9=B4?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - code rabbit 리뷰 반영: https://github.com/solid-connection/solid-connect-server/pull/479#discussion_r2296724585 --- .../auth/token/JwtTokenProvider.java | 6 +++++- .../auth/service/JwtTokenProviderTest.java | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index 3e147f0b6..c7e76985f 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -45,7 +45,11 @@ private String generateJwtTokenValue(String subject, Map claims, @Override public Subject parseSubject(String token) { - return new Subject(parseJwtClaims(token).getSubject()); + String subject = parseJwtClaims(token).getSubject(); + if (subject == null || subject.isBlank()) { + throw new CustomException(INVALID_TOKEN); + } + return new Subject(subject); } @Override diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index 20d8c5460..86b616809 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -109,6 +109,24 @@ class 토큰으로부터_subject_를_추출한다 { .isInstanceOf(CustomException.class) .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); } + + @Test + void subject_가_없는_토큰의_subject_를_추출하면_예외가_발생한다() { + // given + Claims claims = Jwts.claims(new HashMap<>()); + String subjectNotExistingToken = createExpiredToken(claims); + String subjectBlankToken = tokenProvider.generateToken(new Subject(" "), expectedExpireTime); + + // when, then + assertAll( + () -> assertThatCode(() -> tokenProvider.parseSubject(subjectNotExistingToken)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()), + () -> assertThatCode(() -> tokenProvider.parseSubject(subjectBlankToken)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()) + ); + } } @Nested From 66f4cb4b035d99fee4a1940eb7a2f03b56475b40 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 22:37:19 +0900 Subject: [PATCH 22/23] =?UTF-8?q?refactor:=20blacklist=20token=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=8B=9C=20TTL=20=EC=84=A4=EC=A0=95=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - code rabbit 리뷰 반영: https://github.com/solid-connection/solid-connect-server/pull/479#discussion_r2296724588 --- .../solidconnection/auth/token/TokenBlackListService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java index 9777ec0c3..b17635724 100644 --- a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -2,6 +2,7 @@ import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.security.filter.BlacklistChecker; +import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -22,7 +23,12 @@ public class TokenBlackListService implements BlacklistChecker { * */ public void addToBlacklist(String accessToken) { String blackListKey = createKey(accessToken); - redisTemplate.opsForValue().set(blackListKey, SIGN_OUT_VALUE); + redisTemplate.opsForValue().set( + blackListKey, + SIGN_OUT_VALUE, + tokenProperties.blackList().expireTime(), + TimeUnit.MICROSECONDS + ); } @Override From 21542c629fc1f1c6b7cec8bfcdf3283d94d796f2 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 25 Aug 2025 22:58:46 +0900 Subject: [PATCH 23/23] =?UTF-8?q?refactor:=20duration=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=8B=9C=EA=B0=84=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - application.yml을 보면 알다시피 더 명시적이게 바뀐다는 장점도 있다. - code rabbit 리뷰 반영: https://github.com/solid-connection/solid-connect-server/pull/479#discussion_r2296724581 --- .../auth/controller/RefreshTokenCookieManager.java | 7 ++++--- .../solidconnection/auth/service/TokenProvider.java | 5 +++-- .../auth/service/signup/PasswordTemporaryStorage.java | 5 +---- .../solidconnection/auth/token/JwtTokenProvider.java | 9 +++++---- .../solidconnection/auth/token/RedisTokenStorage.java | 4 +--- .../auth/token/TokenBlackListService.java | 4 +--- .../auth/token/config/TokenConfig.java | 4 +++- .../auth/token/config/TokenProperties.java | 3 ++- src/main/resources/secret | 2 +- .../controller/RefreshTokenCookieManagerTest.java | 2 +- .../auth/service/JwtTokenProviderTest.java | 11 ++++++----- .../auth/service/signup/SignUpTokenProviderTest.java | 3 ++- .../auth/token/RedisTokenStorageTest.java | 3 ++- src/test/resources/application.yml | 8 ++++---- 14 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java index c036bdb81..6b22aa326 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java +++ b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java @@ -8,6 +8,7 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.time.Duration; import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.boot.web.server.Cookie.SameSite; @@ -26,14 +27,14 @@ public class RefreshTokenCookieManager { private final TokenProperties tokenProperties; public void setCookie(HttpServletResponse response, String refreshToken) { - long tokenExpireTime = tokenProperties.refresh().expireTime(); + Duration tokenExpireTime = tokenProperties.refresh().expireTime(); long cookieMaxAge = convertExpireTimeToCookieMaxAge(tokenExpireTime); setRefreshTokenCookie(response, refreshToken, cookieMaxAge); } - private long convertExpireTimeToCookieMaxAge(long milliSeconds) { + private long convertExpireTimeToCookieMaxAge(Duration tokenExpireTime) { // jwt의 expireTime 단위인 millisecond를 cookie의 maxAge 단위인 second로 변환 - return milliSeconds / 1000; + return tokenExpireTime.toSeconds(); } public void deleteCookie(HttpServletResponse response) { diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index 7b40aa088..32ce4eb8f 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -1,13 +1,14 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.Subject; +import java.time.Duration; import java.util.Map; public interface TokenProvider { - String generateToken(Subject subject, long expiration); + String generateToken(Subject subject, Duration expiration); - String generateToken(Subject subject, Map claims, long expiration); + String generateToken(Subject subject, Map claims, Duration expiration); Subject parseSubject(String token); diff --git a/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java b/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java index 5397f1215..df70465b6 100644 --- a/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java +++ b/src/main/java/com/example/solidconnection/auth/service/signup/PasswordTemporaryStorage.java @@ -2,7 +2,6 @@ import com.example.solidconnection.auth.token.config.TokenProperties; import java.util.Optional; -import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; @@ -20,12 +19,10 @@ public class PasswordTemporaryStorage { public void save(String email, String rawPassword) { String encodedPassword = passwordEncoder.encode(rawPassword); - long expireTime = tokenProperties.signUp().expireTime(); redisTemplate.opsForValue().set( convertToKey(email), encodedPassword, - expireTime, - TimeUnit.MILLISECONDS + tokenProperties.signUp().expireTime() ); } diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index c7e76985f..262cfdcd3 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -9,6 +9,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.time.Duration; import java.util.Date; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -21,20 +22,20 @@ public class JwtTokenProvider implements TokenProvider { private final JwtProperties jwtProperties; @Override - public String generateToken(Subject subject, long expireTime) { + public String generateToken(Subject subject, Duration expireTime) { return generateJwtTokenValue(subject.value(), Map.of(), expireTime); } @Override - public String generateToken(Subject subject, Map customClaims, long expireTime) { + public String generateToken(Subject subject, Map customClaims, Duration expireTime) { return generateJwtTokenValue(subject.value(), customClaims, expireTime); } - private String generateJwtTokenValue(String subject, Map claims, long expireTime) { + private String generateJwtTokenValue(String subject, Map claims, Duration expireTime) { Claims jwtClaims = Jwts.claims().setSubject(subject); jwtClaims.putAll(claims); Date now = new Date(); - Date expiredDate = new Date(now.getTime() + expireTime); + Date expiredDate = new Date(now.getTime() + expireTime.toMillis()); return Jwts.builder() .setClaims(jwtClaims) .setIssuedAt(now) diff --git a/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java b/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java index aeb974417..291bf4ce1 100644 --- a/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java +++ b/src/main/java/com/example/solidconnection/auth/token/RedisTokenStorage.java @@ -5,7 +5,6 @@ import com.example.solidconnection.auth.service.TokenStorage; import com.example.solidconnection.auth.token.config.TokenProperties; import java.util.Optional; -import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -21,8 +20,7 @@ public T saveToken(Subject subject, T token) { redisTemplate.opsForValue().set( createKey(subject, token.getClass()), token.token(), - TokenProperties.getExpireTime(token.getClass()), - TimeUnit.MILLISECONDS + TokenProperties.getExpireTime(token.getClass()) ); return token; } diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java index b17635724..a2636f383 100644 --- a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -2,7 +2,6 @@ import com.example.solidconnection.auth.token.config.TokenProperties; import com.example.solidconnection.security.filter.BlacklistChecker; -import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -26,8 +25,7 @@ public void addToBlacklist(String accessToken) { redisTemplate.opsForValue().set( blackListKey, SIGN_OUT_VALUE, - tokenProperties.blackList().expireTime(), - TimeUnit.MICROSECONDS + tokenProperties.blackList().expireTime() ); } diff --git a/src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java b/src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java index 7cc64766b..07becbb1d 100644 --- a/src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java +++ b/src/main/java/com/example/solidconnection/auth/token/config/TokenConfig.java @@ -1,8 +1,10 @@ package com.example.solidconnection.auth.token.config; +import java.time.Duration; + public record TokenConfig( String storageKeyPrefix, - long expireTime + Duration expireTime ) { } diff --git a/src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java b/src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java index 38fd604cb..cf8a6e9b4 100644 --- a/src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java +++ b/src/main/java/com/example/solidconnection/auth/token/config/TokenProperties.java @@ -5,6 +5,7 @@ import com.example.solidconnection.auth.domain.SignUpToken; import com.example.solidconnection.auth.domain.Token; import jakarta.annotation.PostConstruct; +import java.time.Duration; import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -27,7 +28,7 @@ public void init() { ); } - public static long getExpireTime(Class tokenClass) { + public static Duration getExpireTime(Class tokenClass) { return tokenConfigs.get(tokenClass).expireTime(); } diff --git a/src/main/resources/secret b/src/main/resources/secret index bb3bf0f41..3e972ea5d 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit bb3bf0f4122d10ddacab279a368cf9f06d6f6dbd +Subproject commit 3e972ea5dea923b6fdb67842451c4af41a4abc8b diff --git a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java index 0df140756..57b6ad97e 100644 --- a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java +++ b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java @@ -62,7 +62,7 @@ void setUp() { () -> assertThat(header).contains("HttpOnly"), () -> assertThat(header).contains("Secure"), () -> assertThat(header).contains("Path=/"), - () -> assertThat(header).contains("Max-Age=" + tokenProperties.refresh().expireTime() / 1000), + () -> assertThat(header).contains("Max-Age=" + tokenProperties.refresh().expireTime().toSeconds()), () -> assertThat(header).contains("Domain=" + domain), () -> assertThat(header).contains("SameSite=" + SameSite.LAX.attributeValue()) ); diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index 86b616809..506019478 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -13,6 +13,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.time.Duration; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -32,7 +33,7 @@ class JwtTokenProviderTest { private JwtProperties jwtProperties; private final Subject expectedSubject = new Subject("subject123"); - private final long expectedExpireTime = 10000L; + private final Duration expectedExpireTime = Duration.ofMinutes(10); @Nested class 토큰을_생성한다 { @@ -44,7 +45,7 @@ class 토큰을_생성한다 { // then - subject와 만료 시간이 일치하는지 검증 Subject actualSubject = tokenProvider.parseSubject(token); - long actualExpireTime = getActualExpireTime(token); + Duration actualExpireTime = getActualExpireTime(token); assertAll( () -> assertThat(actualSubject).isEqualTo(expectedSubject), () -> assertThat(actualExpireTime).isEqualTo(expectedExpireTime) @@ -65,7 +66,7 @@ class 토큰을_생성한다 { // then - subject와 커스텀 클레임이 일치하는지 검증 Subject actualSubject = tokenProvider.parseSubject(token); - long actualExpireTime = getActualExpireTime(token); + Duration actualExpireTime = getActualExpireTime(token); assertAll( () -> assertThat(actualSubject).isEqualTo(expectedSubject), () -> assertThat(actualExpireTime).isEqualTo(expectedExpireTime), @@ -74,12 +75,12 @@ class 토큰을_생성한다 { ); } - private long getActualExpireTime(String token) { + private Duration getActualExpireTime(String token) { Claims claims = Jwts.parser() .setSigningKey(jwtProperties.secret()) .parseClaimsJws(token) .getBody(); - return claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + return Duration.ofMillis(claims.getExpiration().getTime() - claims.getIssuedAt().getTime()); } } diff --git a/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java index 1e7f03e18..aff6a50d4 100644 --- a/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/signup/SignUpTokenProviderTest.java @@ -18,6 +18,7 @@ import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.time.Duration; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -116,7 +117,7 @@ class 주어진_회원가입_토큰을_검증한다 { // given String wrongAuthType = "카카오"; Map wrongClaim = new HashMap<>(Map.of(authTypeClaimKey, wrongAuthType)); - String wrongAuthTypeClaim = tokenProvider.generateToken(subject, wrongClaim, 10000L); + String wrongAuthTypeClaim = tokenProvider.generateToken(subject, wrongClaim, Duration.ofMinutes(10)); // when & then assertThatCode(() -> signUpTokenProvider.validateSignUpToken(wrongAuthTypeClaim)) diff --git a/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java b/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java index 9aa03ac61..ff06979b2 100644 --- a/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java +++ b/src/test/java/com/example/solidconnection/auth/token/RedisTokenStorageTest.java @@ -8,6 +8,7 @@ import com.example.solidconnection.auth.domain.Token; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.Duration; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -32,7 +33,7 @@ class RedisTokenStorageTest { @BeforeEach void setUp() { subject = new Subject("subject123"); - expectedToken = new AccessToken(tokenProvider.generateToken(subject, 100000L)); + expectedToken = new AccessToken(tokenProvider.generateToken(subject, Duration.ofMinutes(10))); tokenClass = expectedToken.getClass(); } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 06e370314..9d255b46b 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -87,15 +87,15 @@ news: default-thumbnail-url: "default-thumbnail-url" token: access: - expire-time: 600000 # 10minutes storage-key-prefix: "ACCESS" + expire-time: 10m refresh: cookie-domain: "test.domain.com" - expire-time: 600000 # 10minutes storage-key-prefix: "REFRESH" + expire-time: 10m sign-up: - expire-time: 600000 # 10minutes storage-key-prefix: "SIGN_UP" + expire-time: 10m black-list: - expire-time: 600000 # 10minutes storage-key-prefix: "BLACKLIST" + expire-time: 10m