-
Notifications
You must be signed in to change notification settings - Fork 1
Refactor: 레디스 적용 #93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor: 레디스 적용 #93
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,16 @@ | ||||||||||||||||||||||
| package naughty.tuzamate.auth.service; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import jakarta.annotation.PostConstruct; | ||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||
| import naughty.tuzamate.auth.entity.RefreshToken; | ||||||||||||||||||||||
| import naughty.tuzamate.auth.repository.RefreshTokenRepository; | ||||||||||||||||||||||
| import org.springframework.data.redis.core.RedisTemplate; | ||||||||||||||||||||||
| import org.springframework.data.redis.core.ValueOperations; | ||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import java.time.Duration; | ||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||
| import java.util.Optional; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -16,23 +20,31 @@ | |||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||
| public class RefreshTokenService { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private final RefreshTokenRepository refreshTokenRepository; | ||||||||||||||||||||||
| private final RedisTemplate<String, String> redisTemplate; | ||||||||||||||||||||||
| private ValueOperations<String, String> valueOperations; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public void saveRefreshToken(Long userId, String token, LocalDateTime expire) { | ||||||||||||||||||||||
| @PostConstruct | ||||||||||||||||||||||
| private void init() { | ||||||||||||||||||||||
| valueOperations = redisTemplate.opsForValue(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| deleteToken(userId); | ||||||||||||||||||||||
| public void saveRefreshToken(Long userId, String token, LocalDateTime expire) { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| RefreshToken refreshToken = new RefreshToken(token, userId, expire); | ||||||||||||||||||||||
| refreshTokenRepository.save(refreshToken); | ||||||||||||||||||||||
| String key = "refreshToken:" + userId; | ||||||||||||||||||||||
| Duration ttl = Duration.between(LocalDateTime.now(), expire); | ||||||||||||||||||||||
| valueOperations.set(key, token, ttl); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+34
to
45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against non-positive TTL when saving tokens. If expire <= now, Duration.between(...) is zero/negative. RedisTemplate.set with non-positive TTL will either error or instantly expire. Apply this diff: - Duration ttl = Duration.between(LocalDateTime.now(), expire);
- valueOperations.set(key, token, ttl);
+ Duration ttl = Duration.between(LocalDateTime.now(), expire);
+ if (ttl.isZero() || ttl.isNegative()) {
+ log.warn("Skip saving refresh token: non-positive TTL for userId={}, expire={}", userId, expire);
+ redisTemplate.delete(key);
+ return;
+ }
+ valueOperations.set(key, token, ttl);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @coderabbitai return; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RCNR 맞습니다! 두 지적 모두 정확합니다.
더 나은 해결책을 제안드립니다: public void saveRefreshToken(Long userId, String token, LocalDateTime expire) {
String key = "refreshToken:" + userId;
Duration ttl = Duration.between(LocalDateTime.now(), expire);
if (ttl.isZero() || ttl.isNegative()) {
log.error("Invalid expire time for refresh token: userId={}, expire={}, current={}",
userId, expire, LocalDateTime.now());
throw new IllegalArgumentException("Refresh token expire time must be in the future");
}
valueOperations.set(key, token, ttl);
}이 방식은:
✏️ Learnings added
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public boolean validateToken(String token) { | ||||||||||||||||||||||
| public Optional<String> getRefreshToken(Long userId) { | ||||||||||||||||||||||
| String key = "refreshToken:" + userId; | ||||||||||||||||||||||
| String token = valueOperations.get(key); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Optional<RefreshToken> refreshToken = refreshTokenRepository.findByToken(token); | ||||||||||||||||||||||
| return refreshToken.isPresent() && refreshToken.get().getExpireDate().isAfter(LocalDateTime.now()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return token != null ? Optional.of(token) : Optional.empty(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public void deleteRefreshToken(Long userId) { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public void deleteToken(Long userId) { | ||||||||||||||||||||||
| refreshTokenRepository.deleteByUserId(userId); | ||||||||||||||||||||||
| String key = "refreshToken:" + userId; | ||||||||||||||||||||||
| redisTemplate.delete(key); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package naughty.tuzamate.global.config; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
| import org.springframework.data.redis.connection.RedisStandaloneConfiguration; | ||
| import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
|
|
||
| @Configuration | ||
| public class RedisConfig { | ||
|
|
||
| @Value("${spring.data.redis.host}") | ||
| private String host; | ||
|
|
||
| @Value("${spring.data.redis.port}") | ||
| private int port; | ||
|
|
||
| @Bean | ||
| public LettuceConnectionFactory redisConnectionFactory() { | ||
| return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port)); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Bean | ||
| public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) { | ||
|
|
||
| RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); | ||
|
|
||
| // redis 연결 | ||
| redisTemplate.setConnectionFactory(redisConnectionFactory); | ||
|
|
||
| // key, value 직렬화 설정 | ||
| StringRedisSerializer serializer = new StringRedisSerializer(); | ||
| redisTemplate.setKeySerializer(serializer); | ||
| redisTemplate.setValueSerializer(serializer); | ||
|
|
||
| // hash key, hash value 직렬화 설정 | ||
| redisTemplate.setHashKeySerializer(serializer); | ||
| redisTemplate.setHashValueSerializer(serializer); | ||
|
|
||
| return redisTemplate; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.