Redis 이용하기 (Refresh 토큰 관리)

Nicky·2024년 2월 14일
0
post-thumbnail

Refresh 토큰을 일반 DB에서 관리하게 된다면, Access 토큰의 재발급 요청마다 DB를 조회해야 하는 비용이 생기게 된다. 이러한 비용을 최소화하기 위한 대안이 필요한데.. 인메모리 데이터베이스가 좋은 선택이 될 수 있다.

1. In-Memory DB

인메모리 데이터베이스는 데이터를 메인 메모리에 저장하고 처리하는 데이터베이스 시스템이다. MySQL, Oracle과 같은 일반적인 디스크 기반 데이터베이스와는 달리, CPU가 직접 액세스하는 RAM(Randome Access Memory)에 저장하므로 빠른 읽기 및 쓰기 속도를 누릴 수 있다.

2. Redis


Redis(Remote Dictionary Server)는 오픈 소스 인메모리 데이터 구조 저장소이며 다음과 같은 특징을 갖고 있다.

  1. Key-Value 형식으로 데이터 저장. 각 데이터는 고유한 키로 식별되며, 해당 키를 사용하여 데이터에 액세스
  2. 인메모리 데이터베이스로서 빠른 읽기와 쓰기 속도 제공. 디스크 기반 데이터베이스보다 높은 처리량 제공
  3. 단순 문자열뿐만 아니라, 리스트, 해시, 셋 등 다양한 데이터 구조 지원. 캐싱, 세션 관리, 메시지 큐 등 다양하게 활용 가능

이러한 특성으로 인해, refresh 토큰의 관리는 Redis로 관리하는것이 여러 이점을 가지게 된다. 이제 Spring에서 Redis를 사용하는 방법에 대해 알아보자.

3. Redis 설치

홈페이지에 들어가서 각자 환경에 맞는 버전을 다운로드하자.

테스트

설치가 완료되면 해당 커멘드로 테스트 해볼 수 있다.
참고로 Redis의 기본 포트는 6379이다.

redis-cli -> ping

4. Spring Boot 세팅

의존성 추가

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

application.properties

spring.data.redis.host=root
spring.data.redis.port=6379

RedisConfig

여기서 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)

5. RedisService

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);
    }

6. 테스트 코드

@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);
                }
        );
    }

}

7. 마치며

지금까지 Redis를 이용하는 이유와 Refresh 토큰을 Redis에서 관리하는 코드를 작성해보았다. 다음 포스팅부터 본격적으로 Spring Security에 JWT 로그인을 적용해보도록 하겠다.

추가로 Spring에서 Redis를 이용할 때, @Transaction을 이용하거나, 비동기 처리를 지원하는 라이브러리를 제공하기도 한다고 한다. 나중에 공부하면 좋을 듯하다.

profile
코딩 연구소

0개의 댓글

관련 채용 정보