
제가 개발 중인 서비스 IDam은 학생과 기업을 매칭하는 플랫폼입니다.
JWT 기반 인증 방식을 사용하며, Refresh Token을 서버 내 메모리(ConcurrentHashMap)에 저장하는 방식으로 관리하고 있었습니다.
@Component
public class RefreshTokenStore {
private final Map<Long, String> refreshTokenStore = new ConcurrentHashMap<>();
public void save(Long userId, String refreshToken) {
refreshTokenStore.put(userId, refreshToken);
}
public String get(Long userId) {
return refreshTokenStore.get(userId);
}
public void delete(Long userId) {
refreshTokenStore.remove(userId);
}
}
이 방식은 간단하고 잘 동작했지만, 다음과 같은 문제점이 있었습니다.
→ 서버 메모리 방식은 단기적으론 괜찮지만, 운영 환경에선 안정성과 확장성에 문제가 생길 수 있다는 한계를 느꼈습니다.
Redis는 메모리 기반 Key-Value 저장소로, 다음과 같은 장점이 있습니다.
제가 생각했던 저의 서비스에서 Refresh Token 관리에 요구되는 조건은 다음과 같습니다.
→ 이 요구사항을 만족하는 저장소는 Redis밖에 없다고 생각하여 Refresh Token 저장소로 도입하였습니다.
→ 또한, 제 서비스는 서버가 1대지만, 미래 확장을 고려해 처음부터 Redis로 관리하자는 판단을 내렸습니다.
제가 Redis로 변경하면서 작성한 코드는 아래와 같습니다.
@Component
public class RefreshTokenStore {
private final RedisTemplate<String, String> redisTemplate;
private final long refreshTokenValidity = 7 * 24 * 60 * 60; // 7일 (초 단위)
public RefreshTokenStore(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 저장 (디바이스별 저장, TTL 설정)
public void save(Long userId, String deviceId, String refreshToken) {
String key = buildKey(userId, deviceId);
redisTemplate.opsForValue().set(key, refreshToken, refreshTokenValidity, TimeUnit.SECONDS);
}
// 조회
public String get(Long userId, String deviceId) {
String key = buildKey(userId, deviceId);
return redisTemplate.opsForValue().get(key);
}
// 삭제
public void delete(Long userId, String deviceId) {
String key = buildKey(userId, deviceId);
redisTemplate.delete(key);
}
// key 생성
private String buildKey(Long userId, String deviceId) {
return "refreshToken:" + userId + ":" + deviceId;
}
}
→ 핵심 변경점:
Map<Long, String> → Redis opsForValue()userId를 key로 사용 (기존 토큰 덮어쓰기 가능)제 서비스(IDam)는 Refresh Token 관리에 Redis를 도입함으로써:
이 모든 장점을 가져올 수 있었습니다.
Redis는 단순 캐시가 아니라, 토큰 관리에 최적화된 저장소였습니다.
지금은 서버 1대지만,
“처음부터 Redis로 관리” → 미래 확장에 대비하고, 운영 안정성을 확보한 최고의 선택이었습니다.
Spring Boot는 application.properties의 spring.redis.host 설정 등을 기반으로
자동으로 RedisConnectionFactory (Redis 연결 팩토리)를 생성합니다.
하지만 Docker 환경에서는
redis) 인식 문제그래서 저는 직접 RedisConnectionFactory를 @Bean으로 등록해서 Redis 연결 객체를 명시적으로 만들어줬습니다.
이렇게 하면 Spring이 RedisTemplate을 생성할 때 이 연결 객체를 사용합니다.
RedisConnectionFactory는 Redis 연결을 생성하고 관리하는 인터페이스이며
LettuceConnectionFactory는 Spring에서 Redis와 연결할 때 사용하는 연결 객체.
// RedisConfig.java
@Configuration
public class RedisConfig {
// application.properties의 spring.redis.host 값을 주입
@Value("${spring.redis.host}")
private String redisHost;
// application.properties의 spring.redis.port 값을 주입
@Value("${spring.redis.port}")
private int redisPort;
// Redis 연결 팩토리 직접 Bean 등록
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
return new LettuceConnectionFactory(config); // Lettuce 클라이언트를 사용해 연결
}
// RedisTemplate에 위의 연결 팩토리 주입
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
이 글은 제가 미래에 “왜 Redis를 Refresh Token 저장소로 선택하며 겪었던 시행착오”를 잊지 않기 위한 기록이며, 비슷한 상황에 처한 개발자들에게 하나의 선택지를 보여주고 싶어서 작성했습니다.