리프레시 토큰 저장소 Redis 적용

2025. 3. 17. 15:27·개발기록/SweetBalance

이번 프로젝트에서는 인증/인가를 JWT를 통해 진행하였다. 초반 구현 단계에서는 MariaDB 내에 RefreshToken 테이블을 추가하여 리프레시 토큰을 관리하였지만 이후 Redis로 토큰 저장소를 변경하여 적용하였다.


기존 RDBMS 토큰 저장소에서의 변경 이유 

Redis로 리프레시 토큰 저장소를 변경한 가장 큰 이유는 TTL이었다. 리프레시 토큰의 경우 생성 시 만료 시간을 설정해주는데, RDBMS 사용 시 스케쥴러를 통해 수동으로 DB 내부 리프레시 토큰 값을 삭제시켜주어야 했기에 Redis의 TTL(Time To Live) 기능을 활용하여 Redis 내에서 자동으로 리프레시 토큰을 삭제하도록 해주었다.

 

추가적으로, Redis는 인메모리 저장 방식을 사용하기에 응답 속도가 RDBMS 보다 10배 이상 빠르기에, 병목 발생 가능성을 낮춰준다.


Redis 적용

Redis Cloud Console & Redis Insight

https://cloud.redis.io

로컬 Redis에서 로직 테스트 이후, 클라우드 환경에서의 Redis 연결을 위해 Redis Cloud Console을 사용하였다. 무료로 Memory usage 30MB 까지 사용이 가능하였다.

 

DBeaver에서 Redis에 대해서는 유료 사용자만 서비스를 제공했기에, Redis Insight를 GUI로 사용하였다.


build.gradle

gradle 파일에 redis implementation을 추가해주었다.

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

application.yml

Redis Cloud Console에서 제공하는 Public endpoint를 통해 host와 port를 입력, password 까지 설정해주었다.

spring:
  data:
    redis:
      host: redis-*****.****.ap-northeast-*-*.ec2.redns.redis-cloud.com
      port: *****
      password: *****

 


RedisConfig 설정

yml 파일에서 설정한 설정 변수를 토대로 Redis 연결을 설정하고  RedisTemplate를 구성하였다.

@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String host;

    @Value("${spring.data.redis.port}")
    private int port;

    @Value("${spring.data.redis.password}")
    private String password;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port);
        redisStandaloneConfiguration.setPassword(password);
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

RefreshTokenRepository 인터페이스

리프레시 토큰 존재 여부 확인, 저장, 삭제 메소드를 정의하였다.

public interface RefreshTokenRepository {

    boolean existsByRefresh(String refresh);

    void deleteByRefresh(String refresh);

    void save(RefreshEntity refreshEntity);
}

RelationalRefreshTokenRepository 구현체

Redis Repository 적용 이전에, 기존에 JpaRepository를 사용하던 것에서 RefreshTokenRepository 인터페이스를 구현하는 방식으로 구조를 바꾸어 언제든 RDBMS로 변경할 수 있도록 구현해두고 싶었다. 이를 위해 어댑터 패턴을 통해 JpaRepository의 메소드들을 RefreshTokenRepository 인터페이스의 메소드와 대응하여 연결해주었다.

public class RelationalRefreshTokenRepository implements RefreshTokenRepository {

    private final JpaRefreshTokenRepository jpaRefreshTokenRepository;

    @Autowired
    public RelationalRefreshTokenRepository(JpaRefreshTokenRepository jpaRefreshTokenRepository) {
        this.jpaRefreshTokenRepository = jpaRefreshTokenRepository;
    }

    @Override
    public boolean existsByRefresh(String refresh) {
        return jpaRefreshTokenRepository.existsByRefresh(refresh);
    }

    @Override
    public void deleteByRefresh(String refresh) {
        jpaRefreshTokenRepository.deleteByRefresh(refresh);
    }

    @Override
    public void save(RefreshEntity refreshEntity) {
        jpaRefreshTokenRepository.save(refreshEntity);
    }
}

RedisRefreshTokenRepository 구현체

리프레시 토큰 값에 prefix를 앞에 붙여 key 값으로 사용하고, expire() 메소드를 통해 TTL을 설정해주었다.

블랙 리스트 등 추가적인 기능 확장을 열어두기 위하여 email 값을 value로 저장하였다. 

@Repository
@Primary
public class RedisRefreshTokenRepository implements RefreshTokenRepository {
    private final RedisTemplate<String, Object> redisTemplate;
    private static final String KEY_PREFIX = "refresh_token:";

    public RedisRefreshTokenRepository(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean existsByRefresh(String refresh) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(KEY_PREFIX + refresh));
    }

    @Override
    public void deleteByRefresh(String refresh) {
        redisTemplate.delete(KEY_PREFIX + refresh);
    }

    @Override
    public void save(RefreshEntity refreshEntity) {
        String key = KEY_PREFIX + refreshEntity.getRefresh();
        redisTemplate.opsForValue().set(key, refreshEntity.getEmail());
        if (refreshEntity.getExpiration() != null) {
            Duration duration = Duration.between(LocalDateTime.now(), refreshEntity.getExpiration());
            redisTemplate.expire(key, duration);
        }
    }
}

마치며

리프레시 토큰이라는 특정 형식의 도메인에 알맞은 저장 방식인 Redis을 사용하는 것은 좋은 경험이었다. 이후에 세션 기반 인증 방식으로 프로젝트를 진행하게 된다면 그때에도 Redis를 적용하고 싶다. 그리고 Redis Insight UI가 생각보다 너무 깔끔해서 좋았다..

'개발기록/SweetBalance' 카테고리의 다른 글
  • 셀레니움을 활용한 크롤링 시스템 구현
  • SecurityConfig 설정
  • Spring Security와 JWT를 통한 인증/인가 로직 구현
kisusu
kisusu
  • kisusu
    KISUSU
    kisusu
  • 전체
    오늘
    어제
    • 분류 전체보기 (9)
      • 개발기록 (4)
        • SweetBalance (4)
      • 개발공부 (2)
      • 개념정리 (2)
      • 알고리즘 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    개발자 유미
    redis
    인가
    인증
    개념정리
    CRUD
    JWT
    리프레시 토큰
    CORS
    스프링
    NoSQL
    Selenium
    셀레니움
    node.js
    SecurityConfig
    Top-Down
    통합 로그인 구현
    스프링 시큐리티
    mongoDB
    개발공부
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
kisusu
리프레시 토큰 저장소 Redis 적용
상단으로

티스토리툴바