From 47863e13299882d53cf53cd937011f5f1bf553ce Mon Sep 17 00:00:00 2001 From: "Hyeonjae.kim" Date: Tue, 21 Jan 2025 17:38:37 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20Implement=20RedisInM?= =?UTF-8?q?emoryCache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/common/cache/InMemoryCache.java | 1 - .../common/cache/RedisInMemoryCache.java | 58 +++++++++++++++++++ .../common/cache/SpringInMemoryCache.java | 1 - 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java diff --git a/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java index 4ec4545a..87c1147d 100644 --- a/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java @@ -14,5 +14,4 @@ public interface InMemoryCache { Optional get(K key); Optional> getEntry(K key); void remove(K key); - void cleanup(); } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java new file mode 100644 index 00000000..e15bda72 --- /dev/null +++ b/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java @@ -0,0 +1,58 @@ +package com.run_us.server.global.common.cache; + +import java.time.Duration; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; + +@RequiredArgsConstructor +public class RedisInMemoryCache implements InMemoryCache { + private final RedisTemplate> cache; + + @Override + public void put(K key, V value) { + CacheEntry entry = CacheEntry.permanent(value); + cache.opsForValue().set(key, entry); + } + + @Override + public void put(K key, V value, Duration ttl) { + CacheEntry entry = CacheEntry.withTtl(value, ttl); + cache.opsForValue().set(key, entry, ttl); + } + + @Override + public boolean putIfAbsent(K key, V value) { + return Boolean.TRUE.equals( + cache.opsForValue().setIfAbsent(key, CacheEntry.permanent(value))); + } + + @Override + public boolean putIfAbsent(K key, V value, Duration ttl) { + return Boolean.TRUE.equals( + cache.opsForValue().setIfAbsent(key, CacheEntry.withTtl(value, ttl), ttl)); + } + + @Override + public Optional get(K key) { + CacheEntry entry = cache.opsForValue().get(key); + if (entry == null) { + return Optional.empty(); + } + return Optional.of(entry.value()); + } + + @Override + public Optional> getEntry(K key) { + CacheEntry entry = cache.opsForValue().get(key); + if (entry == null) { + return Optional.empty(); + } + return Optional.of(entry); + } + + @Override + public void remove(K key) { + cache.delete(key); + } +} diff --git a/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java index b417adcc..eac8995a 100644 --- a/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java @@ -82,7 +82,6 @@ public void remove(K key) { cache.remove(key); } - @Override public void cleanup() { cache.entrySet().removeIf(entry -> entry.getValue().expiresAt() != null && From 402fc9f9852c88276cdf48470661c499a569b6d8 Mon Sep 17 00:00:00 2001 From: "Hyeonjae.kim" Date: Wed, 22 Jan 2025 13:55:11 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20Implement=20naive=20?= =?UTF-8?q?token=20refresh=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/user/controller/AuthController.java | 8 ++++++++ .../controller/model/request/AuthRefreshRequest.java | 12 ++++++++++++ .../model/response/UserHttpResponseCode.java | 1 + .../server/domains/user/domain/AuthResultType.java | 1 + .../server/domains/user/service/UserAuthService.java | 8 ++++++++ 5 files changed, 30 insertions(+) create mode 100644 src/main/java/com/run_us/server/domains/user/controller/model/request/AuthRefreshRequest.java diff --git a/src/main/java/com/run_us/server/domains/user/controller/AuthController.java b/src/main/java/com/run_us/server/domains/user/controller/AuthController.java index 769d2cc8..e708816b 100644 --- a/src/main/java/com/run_us/server/domains/user/controller/AuthController.java +++ b/src/main/java/com/run_us/server/domains/user/controller/AuthController.java @@ -43,4 +43,12 @@ public SuccessResponse signupAndLogin( return SuccessResponse.of( UserHttpResponseCode.SIGNUP_SUCCESS, new AuthResponse(authResult.tokenPair())); } + + @PreAuthorize("permitAll()") + @PostMapping("/refresh") + public SuccessResponse refresh(@Valid @RequestBody AuthRefreshRequest request) { + AuthResult authResult = userAuthService.refresh(request.getRefreshToken()); + return SuccessResponse.of( + UserHttpResponseCode.REFRESH_SUCCESS, new AuthResponse(authResult.tokenPair())); + } } diff --git a/src/main/java/com/run_us/server/domains/user/controller/model/request/AuthRefreshRequest.java b/src/main/java/com/run_us/server/domains/user/controller/model/request/AuthRefreshRequest.java new file mode 100644 index 00000000..811a309c --- /dev/null +++ b/src/main/java/com/run_us/server/domains/user/controller/model/request/AuthRefreshRequest.java @@ -0,0 +1,12 @@ +package com.run_us.server.domains.user.controller.model.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class AuthRefreshRequest { + @NotBlank + private String refreshToken; +} \ No newline at end of file diff --git a/src/main/java/com/run_us/server/domains/user/controller/model/response/UserHttpResponseCode.java b/src/main/java/com/run_us/server/domains/user/controller/model/response/UserHttpResponseCode.java index 64fcfe6d..78e105b0 100644 --- a/src/main/java/com/run_us/server/domains/user/controller/model/response/UserHttpResponseCode.java +++ b/src/main/java/com/run_us/server/domains/user/controller/model/response/UserHttpResponseCode.java @@ -7,6 +7,7 @@ public enum UserHttpResponseCode implements CustomResponseCode { MY_PAGE_DATA_FETCHED("USH2001", "마이페이지 데이터 조회 성공", "마이페이지 데이터 조회 성공"), SIGNUP_SUCCESS("USH2002", "회원가입 성공", "회원가입 성공"), LOGIN_SUCCESS("USH2003", "로그인 성공", "로그인 성공"), + REFRESH_SUCCESS("USH2004", "토큰 재발급 성공", "토큰 재발급 성공"), ; private final String code; diff --git a/src/main/java/com/run_us/server/domains/user/domain/AuthResultType.java b/src/main/java/com/run_us/server/domains/user/domain/AuthResultType.java index d2f91296..504cb7a5 100644 --- a/src/main/java/com/run_us/server/domains/user/domain/AuthResultType.java +++ b/src/main/java/com/run_us/server/domains/user/domain/AuthResultType.java @@ -2,6 +2,7 @@ public enum AuthResultType { LOGIN_SUCCESS, + REFRESH_SUCCESS, SIGNUP_REQUIRED, AUTH_FAILED, SIGNUP_FAILED diff --git a/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java b/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java index 988a43e7..2d7daf75 100644 --- a/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java +++ b/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java @@ -27,6 +27,7 @@ public class UserAuthService { private final OAuthInfoRepository oAuthInfoRepository; private final OAuthTokenRepository oAuthTokenRepository; private final JwtService jwtService; + private final UserService userService; @Transactional(readOnly = true) public AuthResult authenticateOAuth(String rawToken, SocialProvider provider) { @@ -54,6 +55,13 @@ public AuthResult signupAndLogin(String rawToken, SocialProvider provider, Profi } } + @Transactional(readOnly = true) + public AuthResult refresh(String refreshToken) { + String userPublicId = jwtService.getUserIdFromAccessToken(refreshToken); + User user = userService.getUserByPublicId(userPublicId); + return new AuthResult(AuthResultType.REFRESH_SUCCESS, login(user)); + } + private TokenPair login(User user) { return jwtService.generateTokenPair(user); } From 6c9d07f054ca7fe88ce9a57a60b5365aa369d51b Mon Sep 17 00:00:00 2001 From: "Hyeonjae.kim" Date: Wed, 22 Jan 2025 14:27:28 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20Align?= =?UTF-8?q?=20RedisInMemoryCache=20implements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/common/cache/InMemoryCache.java | 3 +- .../common/cache/RedisInMemoryCache.java | 31 ++++++++++--------- .../common/cache/SpringInMemoryCache.java | 5 ++- .../server/global/config/CacheConfig.java | 9 ++++++ .../server/global/config/RedisConfig.java | 4 +-- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java index 87c1147d..b1db05e2 100644 --- a/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java @@ -13,5 +13,6 @@ public interface InMemoryCache { Optional get(K key); Optional> getEntry(K key); - void remove(K key); + + boolean remove(K key); } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java index e15bda72..03420465 100644 --- a/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java @@ -7,52 +7,53 @@ @RequiredArgsConstructor public class RedisInMemoryCache implements InMemoryCache { - private final RedisTemplate> cache; + private final RedisTemplate cache; @Override public void put(K key, V value) { - CacheEntry entry = CacheEntry.permanent(value); - cache.opsForValue().set(key, entry); + cache.opsForValue().set(key, value); } @Override public void put(K key, V value, Duration ttl) { - CacheEntry entry = CacheEntry.withTtl(value, ttl); - cache.opsForValue().set(key, entry, ttl); + cache.opsForValue().set(key, value, ttl); } @Override public boolean putIfAbsent(K key, V value) { return Boolean.TRUE.equals( - cache.opsForValue().setIfAbsent(key, CacheEntry.permanent(value))); + cache.opsForValue().setIfAbsent(key, value)); } @Override public boolean putIfAbsent(K key, V value, Duration ttl) { return Boolean.TRUE.equals( - cache.opsForValue().setIfAbsent(key, CacheEntry.withTtl(value, ttl), ttl)); + cache.opsForValue().setIfAbsent(key, value, ttl)); } @Override public Optional get(K key) { - CacheEntry entry = cache.opsForValue().get(key); - if (entry == null) { + V value = cache.opsForValue().get(key); + if (value == null) { return Optional.empty(); } - return Optional.of(entry.value()); + return Optional.of(value); } @Override public Optional> getEntry(K key) { - CacheEntry entry = cache.opsForValue().get(key); - if (entry == null) { + V value = cache.opsForValue().get(key); + if (value == null) { return Optional.empty(); } - return Optional.of(entry); + Long ttl = cache.getExpire(key); + return Optional.of( + CacheEntry.withTtl(value, Duration.ofSeconds(ttl))); } @Override - public void remove(K key) { - cache.delete(key); + public boolean remove(K key) { + return Boolean.TRUE.equals( + cache.delete(key)); } } diff --git a/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java index eac8995a..8ac2039e 100644 --- a/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java @@ -65,7 +65,6 @@ public Optional get(K key) { return Optional.of(entry.value()); } - @Override public Optional> getEntry(K key) { CacheEntry entry = cache.get(key); if (entry == null || entry.isExpired()) { @@ -78,8 +77,8 @@ public Optional> getEntry(K key) { } @Override - public void remove(K key) { - cache.remove(key); + public boolean remove(K key) { + return cache.remove(key) != null; } public void cleanup() { diff --git a/src/main/java/com/run_us/server/global/config/CacheConfig.java b/src/main/java/com/run_us/server/global/config/CacheConfig.java index 0bece3d1..65558e60 100644 --- a/src/main/java/com/run_us/server/global/config/CacheConfig.java +++ b/src/main/java/com/run_us/server/global/config/CacheConfig.java @@ -3,11 +3,13 @@ import com.run_us.server.domains.crew.domain.CrewPrincipal; import com.run_us.server.domains.user.domain.UserPrincipal; import com.run_us.server.global.common.cache.InMemoryCache; +import com.run_us.server.global.common.cache.RedisInMemoryCache; import com.run_us.server.global.common.cache.SpringInMemoryCache; import com.run_us.server.domains.user.domain.TokenStatus; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @@ -60,4 +62,11 @@ public InMemoryCache crewPrincipalCache( Duration.ofSeconds(cleanupIntervalSeconds) ); } + + @Bean + public InMemoryCache generalStringCache( + RedisTemplate redisTemplate + ) { + return new RedisInMemoryCache<>(redisTemplate); + } } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/global/config/RedisConfig.java b/src/main/java/com/run_us/server/global/config/RedisConfig.java index 43669378..4419fb77 100644 --- a/src/main/java/com/run_us/server/global/config/RedisConfig.java +++ b/src/main/java/com/run_us/server/global/config/RedisConfig.java @@ -31,8 +31,8 @@ public RedisConnectionFactory redisConnectionFactory() { } @Bean - public RedisTemplate redisTemplate() { - RedisTemplate redisTemplate = new RedisTemplate<>(); + public RedisTemplate cacheTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); From 4b2eb7c448adbd3bc5fab7ee68ddf6b6cd32b8d8 Mon Sep 17 00:00:00 2001 From: "Hyeonjae.kim" Date: Wed, 22 Jan 2025 15:21:54 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20Implement=20refreshT?= =?UTF-8?q?oken=20rotating=20strategy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/user/exception/UserErrorCode.java | 1 + .../domains/user/service/JwtService.java | 30 ++++++++++++------- .../domains/user/service/UserAuthService.java | 15 ++++++++-- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/run_us/server/domains/user/exception/UserErrorCode.java b/src/main/java/com/run_us/server/domains/user/exception/UserErrorCode.java index 1e7b54a4..d6d30bf8 100644 --- a/src/main/java/com/run_us/server/domains/user/exception/UserErrorCode.java +++ b/src/main/java/com/run_us/server/domains/user/exception/UserErrorCode.java @@ -14,6 +14,7 @@ public enum UserErrorCode implements CustomResponseCode { JWT_NOT_FOUND("UEH4012", HttpStatus.UNAUTHORIZED, "JWT 토큰이 존재하지 않습니다.", "JWT 토큰이 존재하지 않습니다."), JWT_EXPIRED("UEH4013", HttpStatus.UNAUTHORIZED, "JWT 토큰이 만료되었습니다.", "JWT 토큰이 만료되었습니다."), JWT_BROKEN("UEH4014", HttpStatus.UNAUTHORIZED, "JWT 토큰이 손상되었습니다", "JWT 토큰이 손상되었습니다"), + REFRESH_FAILED("UEH4015", HttpStatus.UNAUTHORIZED, "리프레시 토큰이 만료되었습니다.", "리프레시 토큰이 만료되었습니다."), // 404 USER_NOT_FOUND("UEH4041", HttpStatus.NOT_FOUND, "사용자를 찾을 수 없음", "사용자를 찾을 수 없음"),; diff --git a/src/main/java/com/run_us/server/domains/user/service/JwtService.java b/src/main/java/com/run_us/server/domains/user/service/JwtService.java index 6ff34e72..b66bfdaf 100644 --- a/src/main/java/com/run_us/server/domains/user/service/JwtService.java +++ b/src/main/java/com/run_us/server/domains/user/service/JwtService.java @@ -7,15 +7,19 @@ import com.run_us.server.domains.user.domain.TokenPair; import com.run_us.server.domains.user.domain.User; import com.run_us.server.domains.user.service.verifier.TokenVerifierFactory; +import com.run_us.server.global.common.cache.InMemoryCache; import java.util.Date; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class JwtService { private static final String ISSUER = "RunUSAuthService"; private final TokenVerifierFactory tokenVerifierFactory; + private final InMemoryCache refreshTokenCache; @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expiration}") @@ -23,10 +27,6 @@ public class JwtService { @Value("${jwt.refresh.expiration}") private long jwtRefreshExpiration; - public JwtService(TokenVerifierFactory tokenVerifierFactory) { - this.tokenVerifierFactory = tokenVerifierFactory; - } - public String generateAccessToken(User user) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtExpiration); @@ -43,13 +43,21 @@ public String generateRefreshToken(User user) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtRefreshExpiration); - return JWT.create() - .withSubject(user.getPublicId()) - .withIssuedAt(now) - .withExpiresAt(expiryDate) - .withIssuer(ISSUER) - .withClaim("tokenType", "refresh") - .sign(Algorithm.HMAC256(jwtSecret)); + String refreshToken = JWT.create() + .withSubject(user.getPublicId()) + .withIssuedAt(now) + .withExpiresAt(expiryDate) + .withIssuer(ISSUER) + .withClaim("tokenType", "refresh") + .sign(Algorithm.HMAC256(jwtSecret)); + + refreshTokenCache.put("auth:refresh:"+user.getPublicId(), refreshToken); + return refreshToken; + } + + public boolean nonceRefreshToken(String refreshToken) { + String userPublicId = getUserIdFromAccessToken(refreshToken); + return refreshTokenCache.remove("auth:refresh:"+userPublicId); } public TokenPair generateTokenPair(User user) { diff --git a/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java b/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java index 2d7daf75..857c0225 100644 --- a/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java +++ b/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java @@ -57,9 +57,18 @@ public AuthResult signupAndLogin(String rawToken, SocialProvider provider, Profi @Transactional(readOnly = true) public AuthResult refresh(String refreshToken) { - String userPublicId = jwtService.getUserIdFromAccessToken(refreshToken); - User user = userService.getUserByPublicId(userPublicId); - return new AuthResult(AuthResultType.REFRESH_SUCCESS, login(user)); + if (jwtService.nonceRefreshToken(refreshToken)) { + throw UserAuthException.of(UserErrorCode.REFRESH_FAILED); + } + + String userPublicId = jwtService.getUserIdFromAccessToken(refreshToken); + + User user = userService.getUserByPublicId(userPublicId); + if (user == null) { + throw UserAuthException.of(UserErrorCode.USER_NOT_FOUND); + } + + return new AuthResult(AuthResultType.REFRESH_SUCCESS, login(user)); } private TokenPair login(User user) { From 28a95f5f65fbf775122baba95c7a1b8e4df2874b Mon Sep 17 00:00:00 2001 From: "Hyeonjae.kim" Date: Wed, 22 Jan 2025 15:40:33 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20Add=20refreshToken?= =?UTF-8?q?=20rotation=20safety=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../run_us/server/domains/user/service/JwtService.java | 2 +- .../server/domains/user/service/UserAuthService.java | 2 +- .../server/global/common/cache/InMemoryCache.java | 1 + .../server/global/common/cache/RedisInMemoryCache.java | 10 ++++++++++ .../global/common/cache/SpringInMemoryCache.java | 9 +++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/run_us/server/domains/user/service/JwtService.java b/src/main/java/com/run_us/server/domains/user/service/JwtService.java index b66bfdaf..841e6509 100644 --- a/src/main/java/com/run_us/server/domains/user/service/JwtService.java +++ b/src/main/java/com/run_us/server/domains/user/service/JwtService.java @@ -57,7 +57,7 @@ public String generateRefreshToken(User user) { public boolean nonceRefreshToken(String refreshToken) { String userPublicId = getUserIdFromAccessToken(refreshToken); - return refreshTokenCache.remove("auth:refresh:"+userPublicId); + return refreshTokenCache.remove("auth:refresh:"+userPublicId, refreshToken); } public TokenPair generateTokenPair(User user) { diff --git a/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java b/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java index 857c0225..6a942395 100644 --- a/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java +++ b/src/main/java/com/run_us/server/domains/user/service/UserAuthService.java @@ -57,7 +57,7 @@ public AuthResult signupAndLogin(String rawToken, SocialProvider provider, Profi @Transactional(readOnly = true) public AuthResult refresh(String refreshToken) { - if (jwtService.nonceRefreshToken(refreshToken)) { + if (!jwtService.nonceRefreshToken(refreshToken)) { throw UserAuthException.of(UserErrorCode.REFRESH_FAILED); } diff --git a/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java index b1db05e2..6a5a41b9 100644 --- a/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/InMemoryCache.java @@ -15,4 +15,5 @@ public interface InMemoryCache { Optional> getEntry(K key); boolean remove(K key); + boolean remove(K key, V value); } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java index 03420465..cf33c22c 100644 --- a/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/RedisInMemoryCache.java @@ -56,4 +56,14 @@ public boolean remove(K key) { return Boolean.TRUE.equals( cache.delete(key)); } + + @Override + public boolean remove(K key, V value) { + V currentValue = cache.opsForValue().get(key); + if (value == null || !value.equals(currentValue)) { + return false; + } + return Boolean.TRUE.equals( + cache.delete(key)); + } } diff --git a/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java b/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java index 8ac2039e..175a7348 100644 --- a/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java +++ b/src/main/java/com/run_us/server/global/common/cache/SpringInMemoryCache.java @@ -81,6 +81,15 @@ public boolean remove(K key) { return cache.remove(key) != null; } + @Override + public boolean remove(K key, V value) { + CacheEntry entry = cache.get(key); + if(entry == null || !entry.value().equals(value)) { + return false; + } + return cache.remove(key) != null; + } + public void cleanup() { cache.entrySet().removeIf(entry -> entry.getValue().expiresAt() != null && From d08afe41b3582a10bdb886f3783addb98d925242 Mon Sep 17 00:00:00 2001 From: "Hyeonjae.kim" Date: Wed, 22 Jan 2025 15:52:18 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20:=20Apply?= =?UTF-8?q?=20TTL=20on=20refreshTokenCache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/run_us/server/domains/user/service/JwtService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/run_us/server/domains/user/service/JwtService.java b/src/main/java/com/run_us/server/domains/user/service/JwtService.java index 841e6509..f19c50e2 100644 --- a/src/main/java/com/run_us/server/domains/user/service/JwtService.java +++ b/src/main/java/com/run_us/server/domains/user/service/JwtService.java @@ -8,6 +8,7 @@ import com.run_us.server.domains.user.domain.User; import com.run_us.server.domains.user.service.verifier.TokenVerifierFactory; import com.run_us.server.global.common.cache.InMemoryCache; +import java.time.Duration; import java.util.Date; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -51,7 +52,8 @@ public String generateRefreshToken(User user) { .withClaim("tokenType", "refresh") .sign(Algorithm.HMAC256(jwtSecret)); - refreshTokenCache.put("auth:refresh:"+user.getPublicId(), refreshToken); + refreshTokenCache.put("auth:refresh:"+user.getPublicId(), + refreshToken, Duration.ofSeconds(jwtExpiration)); return refreshToken; }