Refresh
토큰을 일반 DB에서 관리하게 된다면, Access
토큰의 재발급 요청마다 DB를 조회해야 하는 비용이 생기게 된다. 이러한 비용을 최소화하기 위한 대안이 필요한데.. 인메모리 데이터베이스가 좋은 선택이 될 수 있다.
인메모리 데이터베이스는 데이터를 메인 메모리에 저장하고 처리하는 데이터베이스 시스템이다. MySQL, Oracle과 같은 일반적인 디스크 기반 데이터베이스와는 달리, CPU가 직접 액세스하는 RAM
(Randome Access Memory)에 저장하므로 빠른 읽기 및 쓰기 속도를 누릴 수 있다.
Redis
(Remote Dictionary Server)는 오픈 소스 인메모리 데이터 구조 저장소이며 다음과 같은 특징을 갖고 있다.
- Key-Value 형식으로 데이터 저장. 각 데이터는 고유한 키로 식별되며, 해당 키를 사용하여 데이터에 액세스
- 인메모리 데이터베이스로서 빠른 읽기와 쓰기 속도 제공. 디스크 기반 데이터베이스보다 높은 처리량 제공
- 단순 문자열뿐만 아니라, 리스트, 해시, 셋 등 다양한 데이터 구조 지원. 캐싱, 세션 관리, 메시지 큐 등 다양하게 활용 가능
이러한 특성으로 인해, refresh 토큰의 관리는 Redis
로 관리하는것이 여러 이점을 가지게 된다. 이제 Spring
에서 Redis
를 사용하는 방법에 대해 알아보자.
홈페이지에 들어가서 각자 환경에 맞는 버전을 다운로드하자.
설치가 완료되면 해당 커멘드로 테스트 해볼 수 있다.
참고로 Redis의 기본 포트는 6379이다.
redis-cli
->ping
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
spring.data.redis.host=root
spring.data.redis.port=6379
여기서 Lettuce
는 Redis 클라이언트 라이브러리 중 하나로 Redis 서버와의 연결을 관리하고 명령을 실행을 도와준다.
@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Autowired
private final RedisProperties redisProperties;
// lettuce를 사용한 Redis 연결을 위한 ConnectionFactory 빈 설정
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// Redis 서버 호스트와 포트 정보를 사용하여 LettuceConnectionFactory 인스턴스 생성
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
// redis-cli 사용을 위한 RedisTemplate 빈 설정
@Bean
public RedisTemplate<String, Object> redisTemplate() {
// RedisTemplate 인스턴스 생성
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// Redis 연결을 위한 ConnectionFactory 설정
redisTemplate.setConnectionFactory(redisConnectionFactory());
// 키(Key)와 값(Value)의 직렬화를 위해 StringRedisSerializer 설정
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
springboot 2.0이상부터는 auto-configuration으로 redisConnectionFactory, RedisTemplate와 같은 빈들이 자동으로 생성되기 때문에 굳이 Configuration을 만들지 않아도 즉시 사용가능하다고 한다.
(https://jeong-pro.tistory.com/175)
refresh 토큰을 관리하는 Service 코드는 다음과 같이 작성하였다.
key
값은 사용자의 이름, 그리고 value
값은 refresh 토큰값으로 정해주었다.
@Service
@RequiredArgsConstructor
public class RedisService {
@Autowired
private final RedisTemplate<String, String> redisTemplate;
public void setRefreshToken(String username, String refreshToken, long expiration) {
redisTemplate.opsForValue().set(username, refreshToken, expiration, TimeUnit.MILLISECONDS);
}
public String getRefreshToken(String username) {
return redisTemplate.opsForValue().get(username);
}
public void deleteRefreshToken(String username) {
redisTemplate.delete(username);
}
}
한가지 눈여겨볼 점이 있다면 Redis는 각 키에 대해 만료 시간을 설정할 수 있다는 점인데, 만약 내가 저장한 Refresh 토큰이 만료된다면 자동으로 삭제되어 조회가 불가해질 것이다.
public void setRefreshToken(String username, String refreshToken, long expiration) {
redisTemplate.opsForValue().set(username, refreshToken, expiration, TimeUnit.MILLISECONDS);
}
@SpringBootTest
class RedisServiceTest {
final String KEY = "username";
final String VALUE = "refreshToken";
final Long DURATION = 6000L;
@Autowired
private RedisService redisService;
@BeforeEach
void setUp() {
redisService.setRefreshToken(KEY, VALUE, DURATION);
}
@AfterEach
void shutDown() {
redisService.deleteRefreshToken(KEY);
}
@Test
@DisplayName("Refresh 토큰 조회")
void saveAndFindTest() {
// when
String findToken = redisService.getRefreshToken(KEY);
// then
assertThat(findToken).isEqualTo(VALUE);
}
@Test
@DisplayName("Refresh 토큰 조회")
void deleteTest() {
// when
redisService.deleteRefreshToken(KEY);
String findValue = redisService.getRefreshToken(KEY);
// then
assertThat(findValue).isEqualTo(null);
}
@Test
@DisplayName("만료시간 이후 토큰 삭제 확인")
void expiredTest() {
// when
String findValue = redisService.getRefreshToken(KEY);
await().pollDelay(Duration.ofMillis(6000L)).untilAsserted(
() -> {
String expiredValue = redisService.getRefreshToken(KEY);
assertThat(expiredValue).isNotEqualTo(findValue);
assertThat(expiredValue).isEqualTo(null);
}
);
}
}
지금까지 Redis를 이용하는 이유와 Refresh 토큰을 Redis에서 관리하는 코드를 작성해보았다. 다음 포스팅부터 본격적으로 Spring Security에 JWT 로그인을 적용해보도록 하겠다.
추가로 Spring에서 Redis를 이용할 때, @Transaction
을 이용하거나, 비동기
처리를 지원하는 라이브러리를 제공하기도 한다고 한다. 나중에 공부하면 좋을 듯하다.