redis를 통해 로그아웃 기능 구현

Redis

Redis는 오픈 소스 기반의 비관계형 DBMS로, Key-Value 구조의 비정형 데이터를 저장하고 관리한다. 이는 우리가 흔히 사용하는 MySQL과는 다르게, 인메모리 데이터 구조를 가진 저장소이다.

MySQL과 같은 전통적인 DB는 데이터를 디스크에 저장한다. 이로 인해 서버가 다운되더라도 데이터는 보존되지만, 데이터를 조회할 때 디스크에서 읽어야 하므로 속도 면에서 불리하다. 반면, Redis는 자주 사용되는 데이터를 메모리에 저장하여 빠른 데이터 접근 속도를 제공한다.

따라서 이번 프로젝트에서는 Redis를 활용하여 사용자의 로그아웃 시 발급된 JWT 토큰을 저장하고, 이를 BlackList로 관리한고, JWT 토큰의 유효 시간 동안 해당 토큰을 Redis에 보관함으로써, 로그아웃 후 토큰이 탈취되더라도 해당 토큰을 악용할 수 없게 했다.

 

1. Docker를 통해 Redis 실행하기

  1. docker pull 을 사용하여 Redis 이미지 다운받기

    docker pull redis
    
  2. pull 받은 이미지로 docker Container 실행하기

    sudo docker run -d --name redis -p 6379:6379 redis
    

여기까지 성공했다면

docker ps -a 명령어를 통해 실행중인 컨테이너에 아래와같이 redis를 실행하는 컨테이너가 생길것이다.

image-20230816132452365

 

2. yml 파일 작성하기

본 프로젝트는 profile 을 사용하여 각 환경에 필요한 yml 파일을 읽게한다.

application.yml 파일은 아래와 같다.

spring:
  profiles:
    group:
      local: localDB, jasypt, oauth, mybatis, aws, redis
      prod: prodDB, jasypt, oauth, mybatis, aws, redis
      test: testDB, jasypt, oauth, mybatis, aws, redis
    active: local

application-redis.yml 에 아래와 같은 정보를 저장한다.

redis:
  pool:
    min-idle: 0
    max-idle: 8
    max-active: 8
  port: 6379
  host: 127.0.0.1 // spring boot 는 따로 container로 실행하지 않았을때

만약 spring 서버도 독립적인 container에서 실행되고 있다면 docker-compose 에서 host 이름을 지정해준다음 위 yml 파일 host에 지정해준 이름을 넣어주면 된다.

 

3. RedisProperties 클래스를 정의

@Component
@ConfigurationProperties(prefix = "redis")
@Getter
@Setter
public class RedisProperties {

    private int port;
    private String host;
  
}

해당 클래스를 통해 우리가 yml에서 지정해준 port 및 host 정보를 가져온다.

@ConfigurationProperties 를 사용하면 application-redis.yml 에 정의한 redis.hostredis.port 설정 정보를 가져와 RedisProperties 에 바인딩 한다.

 

4. RedisConfig 를 정의한다.

@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisConfig {

    private final RedisProperties redisProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
    }

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

        return redisTemplate;
    }
}

RedisConfig 클래스를 통해 우리가 원하는 Redis의 설정정보를 구성해준다. 여기서 설정해준 정보를 통해 Redis 와의 연결 및 데이터 처리방식을 제공한다.

  • @EnableRedisRepositories: Redis를 사용한 데이터 접근을 위해 Spring Data Redis 리포지토리를 활성화한다.
  • redisConnectionFactory(): Redis와의 연결을 위한 ConnectionFactory를 생성한다. 여기서는 Lettuce 라이브러리를 사용하여 연결을 생성한다.
  • redisTemplate(): Redis 데이터 작업을 위한 중심적인 클래스인 RedisTemplate을 정의한다. 이 메서드에서는 키와 값의 직렬화 방식을 StringRedisSerializer로 설정했다.
  • LettuceConnectionFactory: Lettuce는 Redis 클라이언트 중 하나로, 비동기 이벤트 기반의 연결을 제공한다. 여기서는 RedisProperties에서 제공하는 호스트와 포트 정보를 사용하여 연결을 생성한다.
  • StringRedisSerializer: 이 직렬화 방식은 Redis의 키와 값으로 문자열을 사용하려는 경우에 사용한다.

RedisConnectionFactory

  • 역할: RedisConnectionFactory는 Redis 서버와의 연결을 관리하는 인터페이스다. 이 인터페이스를 구현한 클래스는 Redis 서버와의 연결 세션을 생성하고 관리한다.

  • Lettuce 라이브러리:

    • Lettuce는 Redis 클라이언트 중 하나로, 네티(Netty) 기반의 비동기 이벤트 구동 모델을 사용한다.
    • 여러 Redis 노드 구성(예: Sentinel, Cluster)을 지원하며, 연결 풀링이 내장되어 있어 고성능 환경에서도 잘 동작한다.
    • LettuceConnectionFactory는 Lettuce 클라이언트를 사용하여 Redis 서버와의 연결을 생성하고 관리한다.

RedisTemplate

  • 역할: RedisTemplate는 Redis와의 데이터 작업을 추상화하여 제공하는 헬퍼 클래스이다. Redis의 데이터 구조와 명령을 Java 객체와 메서드로 매핑하여, Java에서 Redis 작업을 쉽게 수행할 수 있게 해준다.

  • 직렬화:

    • Redis는 바이트 배열 형태로 데이터를 저장한다. 따라서 Java 객체를 Redis에 저장하거나 조회할 때는 객체를 바이트 배열로 변환하거나 바이트 배열을 객체로 변환하는 작업이 필요하며, 이러한 작업을 직렬화(Serialization) 및 역직렬화(Deserialization)라고 한다.
    • RedisTemplate에서는 키와 값의 직렬화 방식을 설정할 수 있다. 예를 들어, StringRedisSerializer는 문자열 데이터를 바이트 배열로 변환하는 직렬화 방식이다.
  • 활용: RedisTemplate를 사용하면, Redis의 기본 데이터 구조(예: String, List, Set, Hash, ZSet)에 대한 연산을 Java 메서드로 쉽게 수행할 수 있다. 또한, 트랜잭션, 파이프라인, 스크립트 실행 등의 고급 기능도 지원한다.

 

5. Redis Util 정의

최종적으로 RedisUtil 이라는 클래스를 통해 특정 메서드를 사용하여 Redis 에 작업을 할수있게하는 클래스를 정의해준다.

@Component
@RequiredArgsConstructor
public class RedisUtil {
    private final RedisTemplate<String, Object> redisBlackListTemplate;

    public void setBlackList(String key, Object o, int minutes) {
        //Redis에 저장할 데이터 방식을 설정
        redisBlackListTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(o.getClass()));
        //Redis의 String타입 구조에 작업을 수행하기 위한 연산
        redisBlackListTemplate.opsForValue().set(key, o, minutes, TimeUnit.MINUTES);
    }

    public boolean hasKeyBlackList(String key) {
        return Boolean.TRUE.equals(redisBlackListTemplate.hasKey(key));
    }
}

여기까지 했다면, RedisUtil 클래스의 setBlackList 메서드를 통해 우리가 원하는 토큰을 지정해준 시간이 지날때까지 Redis 에 보관을 하며, Redis 에 존재하는 JWT 토큰으로 로그인을 한 상태를 유지할수 없게 만든다.

setBlackList 메서드:

  • Jackson2JsonRedisSerializer: Jackson 라이브러리를 사용하여 Java 객체를 JSON 형식으로 직렬화하거나 JSON을 Java 객체로 역직렬화하는 직렬화 방식이다. 여기서는 저장하려는 객체의 클래스 타입을 기반으로 직렬화 방식을 설정하고 있다.
  • opsForValue().set(key, o, minutes, TimeUnit.MINUTES): Redis의 String 데이터 구조에 값을 저장하는 메서드이다. 지정된 키, 값, 만료 시간, 시간 단위를 사용하여 데이터를 저장한다.
  1. hasKeyBlackList 메서드: 지정된 키가 Redis에 존재하는지 확인하는 메서드이다. Boolean.TRUE.equals()를 사용하여 결과가 true인지 확인하고 있다.

 

6. Redis 내부 데이터 확인하기

  1. Redis를 실행중인 Container 내부에 들어가기

    docker exec -it redis /bin/bash
    
  2. Redis 에 접속하기

    redis-cli
    
  3. Redis 에 저장된 key 확인하기

    keys *
    
  4. 특정 key 에대한 만료시간 확인하기

    ttl <key>
    

위 명령어를 통해 redis에 저장된 토큰을 확인할수 있으며, 해당 토큰을 통해 로그인을 하려할때 filter 를 통해 로그인을 막을수 있다.

+ Recent posts