본문 바로가기

항해99/스터디

[스터디] Refresh Token을 Redis에 저장하는 이유와 적용 방법

Redis?

 

Redis는 디스크가 아닌 메모리에 데이터를 저장하는 In-Memory 방식의 데이터베이스다.

  • Key-Value의 형태의 데이터베이스이기 때문에 적은 메모리로도 데이터를 저장할 수 있으며, 작성 속도가 빠르다
  • Key-Value 형태를 가지고 있기 때문에 키를 알고 있다면 조회 성능이 O(1)까지 나온다는 장점을 가진다
  • 인메모리 DB 방식으로 빠르게 접근이 가능하다
  • 휘발성인 In-Memory DB는 영구적으로 저장될 필요가 없는 Refresh token을 관리하기에 효율이 좋다.
  • 캐시처럼 데이터 만료일을 정할 있다.
In-Memory

 

In-Memory 데이터베이스는 MySQL과 같은 다른 일반 DB들처럼 SSD, HDD와 같은 보조기억장치가 아닌, 프로세서가 직접 액세스할 수 있는 컴퓨터의 주 메모리인 RAM에 데이터를 저장한다.

 

리프레시 토큰에 Redis 적용하는 이유

 

  1. 우선 RDB와는 다르게 TTL(Time-To-Live) 즉, 데이터의 만료일을 지정할 수 있다. TTL을 토큰의 만료일과 똑같이 맞춰두고 관리하면 토큰 만료시 Redis에서 자동으로 삭제 되기 때문에 데이터 관리가 용이하다.
  2. 30분~2시간 단위로 갱신하는 Access Token을 갱신하기 위해 Refresh Token이 필요하다.
    리프레시 토큰은 액세스 토큰을 재발급 하는 과정에서 자주 호출하기 때문에, RDB에 저장하는 것보다 In-Memory DB에 저장해두고 사용하는 것이 훨씬 속도가 빠른 인메모리 기반 Redis를 사용하는 것이 더 효율적이다.
  3. 현재 사용중인 MySql에서는 리프레시 토큰을 만료시키기 위해 주기적으로 만료된 토큰에 대한 삭제 요청을 보내거나 스케쥴러를 사용해야하는데 이 작업이 비효율적이기 때문에 사용한다.
Redis 적용 방법

 

로컬에서 적용한다면 로컬에서 Redis를 설치하고,

서버에 적용을 한다면 EC2, 도커에 Redis를 설치한다.

 

우리 프로젝트에서는 도커 사용했는데, 이 자료를 많이 참고 했다.

https://velog.io/@mj3242/Spring-Redis-%ED%99%9C%EC%9A%A9-access-Token-refresh-Token-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-%EA%B5%AC%ED%98%84

 

Spring & Redis 활용, access Token & refresh Token 인증/인가 구현

Access Token만을 사용할 때의 단점은 다음과 같다.access token을 발급할 때 유효 기간을 정해서 발급을 하게 되는데 이 유효 기간이 지날 때마다 다시 로그인을 해야 한다. 그런데 이 유효 기간이 만

velog.io

 

application.properties

#Redis
spring.data.redis.host=[localhost] or [퍼블릭 ip]
spring.data.redis.port=6379

Gradle에 의존성 추가

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

 

RedisService 생성

@Slf4j
@Component
@RequiredArgsConstructor
public class RedisService {
    private final RedisTemplate<String, Object> redisTemplate;
    private final RedisRefreshTokenRepository redisRefreshTokenRepository;

    public void saveRefreshToken(RedisRefreshToken refreshToken) {
        redisRefreshTokenRepository.save(refreshToken);
    }

    public Optional<RedisRefreshToken> findRefreshToken(String token) {
        return redisRefreshTokenRepository.findById(token);
    }
    public void setValues(String key, String data) {
        ValueOperations<String, Object> values = redisTemplate.opsForValue();
        values.set(key, data);
    }

 

엔티티 생성

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RedisHash(value = "refresh", timeToLive =  1209600 )
public class RedisRefreshToken {

    @Id
    @Schema(description = "refresh token")
    private String token;

    @Schema(description = "토큰 발급 사용자 email 정보")
    private String email;

    @Builder
    public RedisRefreshToken(String token, String email) {
        this.token = token;
        this.email = email;
    }
}

 

RedisConfig

@RequiredArgsConstructor
/*이 클래스가 Spring의 설정 클래스임을 나타냄*/
@Configuration
/*Redis 저장소를 위한 Spring Data Redis 저장소를 활성화*/
@EnableRedisRepositories
public class RedisConfig {
    
    /* Redis를 사용하여 데이터를 저장하고 조회하기 위한 기본환경 설정
    * 1. RedisTemplate은 Redis에 데이터를 저장하고 조회할 수 있는 기본적인 API를 제공
    * 2. RefreshToken 타입의 데이터를 저장하기 위한 별도의 RedisTemplate도 설정되어 있음.*/
    
    private final RedisProperties redisProperties;

    /*LettuceConnectionFactory 생성, Properties에서 가져온 호스트와 포트 정보를 사용하여 Redis에 연결*/
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
    }

    /* serializer 설정으로 redis-cli를 통해 직접 데이터를 조회할 수 있도록 설정
     * 1. 기본 RedisTemplate을 생성
     * 2. 키와 값의 Serializer를 설정
     * 3. StringRedisSerializer: 문자열 키와 값의 직렬화를 위한 Serializer
     * 4. redisConnectionFactory()에서 생성한 연결 팩토리를 설정*/
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory());

        return redisTemplate;
    }

    /* serializer 설정으로 redis-cli를 통해 직접 데이터를 조회할 수 있도록 설정
     * 1. RefreshToken 객체를 값으로 가지는 RedisTemplate을 생성
     * 2. 키는 여전히 문자열로, 값은 RefreshToken 객체로 직렬화
     * 3. Jackson2JsonRedisSerializer: Jackson 라이브러리를 사용하여 JSON 형태로 객체를 직렬화
     * 4. redisConnectionFactory()에서 생성한 연결 팩토리를 설정. */
    @Bean
    public RedisTemplate<String, RefreshToken> refreshTokenRedisTemplate() {
        RedisTemplate<String, RefreshToken> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(RefreshToken.class));
        redisTemplate.setConnectionFactory(redisConnectionFactory());

        return redisTemplate;
    }
}

 

RedisRefreshTokenRepository

public interface RedisRefreshTokenRepository extends CrudRepository<RedisRefreshToken, String> {
    Optional<RedisRefreshToken> findByToken(String token);
    boolean existsByToken(String token);
}

 

CrudRepository를 사용하는 이유

 

CrudRepository는 Spring Data JPA의 일반적인 인터페이스로, CRUD 작업을 위한 기본 메서드를 제공한다.

  1. 일관된 CRUD 메서드 제공: CrudRepository는 이미 CRUD 작업을 위한 다양한 메서드들을 제공합니다. 이를 통해 개발자는 반복적인 CRUD 로직을 직접 구현할 필요 없이 빠르게 데이터 접근을 구현할 수 있다.
  2. 타입 안전성: 제네릭 타입을 사용하여 엔터티 타입과 ID 타입을 명시한다. 이로 인해 타입 안전성이 보장되며, 잘못된 타입으로 인한 런타임 에러를 줄일 수 있다.
  3. 자동 구현: Spring Data JPA는 CrudRepository를 확장하면 자동으로 기본 CRUD 메서드의 구현을 제공한다. 이를 통해 개발자는 복잡한 쿼리나 로직을 직접 작성할 필요 없이 기본 CRUD 작업을 수행할 수 있다.
  4. 확장성: 필요한 경우 CrudRepository의 메서드를 오버라이드하거나, 커스텀 쿼리 메서드를 추가해 리포지토리 확장할 수 있습니다.

이렇게 작성을 했다면, RefreshToken 관리를 하는 클래스로 가서 저장소를 모두 RedisRepository로 변경해주면 된다.