Tools & Libraries/redis

redis로 토큰 재발급과 로그아웃 구현하기 - 2 (Spring boot에 적용하기)

대기업 가고 싶은 공돌이 2024. 7. 25. 21:00

build.gradle에 의존성 추가하기

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

 

Spring data redis를 추가해주었다.

 

application.yml에 설정 추가하기

  redis:
    pool:
      min-idle: 0
      max-idle: 8
      max-active: 8
    port: 6379
#    host: redis
    host: 127.0.0.1

 

여기서 pool은 일정 수의 연결을 만들어 두고 사용 후 다시 반납하는 방식을 의미한다.

 

min idle: 최소 유휴 연결 수를 의미한다. 이는 redis에 연결된 사용자가 한 명도 없을 시 유휴 연결을 하나도 만들지 않는다는 것을 의미한다.

 

max idle: 최대 유휴 연결 수를 의미한다. 이는 연결 풀에서 유지할 수 있는 최대 유휴 연결 수를 의미하며 이 숫자를 초과하면 초과된 연결은 풀에서 제거 된다.

 

max active: 연결 풀에서 최대로 동시에 사용할 수 있는 연결 수를 의미한다.


연결 풀이 뭔지 이해하기 쉽게 간단한 흐름 예시를 들어보겠다.

  1. 초기 상태: 프로그램이 시작될 때 연결 풀에는 유휴 연결이 없다. (min-idle이 0이므로)
  2. 첫 번째 사용자 요청: 첫 번째 사용자가 Redis에 요청을 하면, 연결 풀이 새로운 연결을 생성하여 사용자가 요청을 처리할 수 있게 한다. 요청이 끝나면, 이 연결은 유휴 연결이 되어 연결 풀에 반환 된다.

  3. 후속 사용자 요청: 두 번째 사용자가 요청을 보내면, 연결 풀은 기존 유휴 연결을 사용하여 요청을 처리한다. 요청이 끝나면, 다시 유휴 연결로 반환한다.

  4. 유휴 연결의 유지 및 제거: 사용자가 없는 상태가 지속되면, 연결 풀은 최소 유휴 연결 수 (min-idle)에 따라 유휴 연결을 유지한다.
  •  

정리:

평소에는 연결 풀에 유휴 연결이 존재하지 않는다. 그러나 사용자의 요청이 들어오면 유휴 연결이 생성되고 이는 일정 시간 동안 유지되며 사용자의 요청이 들어올 때마다 재사용 된다. 

redis와의 연결을 매번 생성하고 닫는 것은 상당히 비효율 적이기 때문에 연결 풀을 이용하면 좀 더 유연한 운영이 가능해진다.


port: redis의 포트 번호를 의미한다 redis는 기본적으로 6379 port 번호를 사용한다.

 

host: 로컬에서 사용시 127.0.0.1 로 설정해주면 된다. 주석 처리된 redis는 컨테이너 환경에서 redis를 사용할 때 다음과 같이 설정한다.

 

Redis config 생성

redis와의 연결을 위한 redis template의 bean 등록을 위해 redis config class를 만들어 준다.

 

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

 

RedisConnectionFactory는 인터페이스로서 구현체가 두 개 존재한다.

LettuceConnectionFactory, JedisConnectionFactory가 존재하는데

 

Lectture: 비동기 + non block 방식으로 멀티 스레스 환경인 어플리케이션에 적합하며 병목 현상을 방지할 수 있다.

 

Jedis: 동기 + bolck 방식으로 한 가지 요청이 들어왔을 때 해당 요청이 반환 되기 전 까지 클라이언트는 다른 동작을 수행할 수 없어 비효율 적이다. 

 

더보기

여기서 redis는 싱글 스레드인데 어떻게 비동기 방식 + non block 방식을 지원하는지 의문이 생길 수 있다.

찾아본 결과 이벤트 큐를 만들어 사용자의 요청을 이벤트 큐에 저장시킨 후 해당 요청이 callback 될 때까지 다른 요청을 수행하는 방식으로 동작한다고 한다.

 

위와 같은 특징을 가지고 있으니 우리는 더 효율적인 Lectture 클래스를 선택하도록 하자.

 

redisTemplate에 추가 설정들을 넣어 더 세밀한 제어도 가능하다.

 

Redis Util 생성

redis를 사용하기 쉽게 여러 메소드를 정의해 놓자

 

@Component
@RequiredArgsConstructor
public class RedisUtil {

    private final RedisTemplate<String, Object> redisTemplate;
    private final RedisTemplate<String, Object> redisBlackListTemplate;

    public void set(String key, Object o, int minutes) {
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(o.getClass()));
        redisTemplate.opsForValue().set(key, o, minutes, TimeUnit.MINUTES);
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public boolean delete(String key) {
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

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

    public void setBlackList(String key, Object o, int minutes) {
        redisBlackListTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(o.getClass()));
        redisBlackListTemplate.opsForValue().set(key, o, minutes, TimeUnit.MINUTES);
    }

    public Object getBlackList(String key) {
        return redisBlackListTemplate.opsForValue().get(key);
    }

    public boolean deleteBlackList(String key) {
        return Boolean.TRUE.equals(redisBlackListTemplate.delete(key));
    }

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

 

기본적인 crud 이외에 액세스 토큰이 탈취 당했을 경우를 대비하여 blacklist 관련 메소드들도 정의해 주었다.

 

단순하게 액세스 토큰과 리프레쉬 토큰을 삭제하는 것만으로는 액세스 토큰이 탈취당했을 경우 보안 api에 접근하는 것을 방지할 수 없기에 로그아웃 시 해당 액세스 토큰을 블랙리스트로 등록시켜 접근 자체를 차단시키는 방식으로 구현하고자 한다.

 

api 구현 및 Spring Security Filter에 적용하는 방법은 다음 글에서 정리하도록 하겠다.

 

 

전체 코드 보기

https://github.com/Fiurinee-Tave/Server