Springboot

[Spring + Redis] 비밀번호 설정 및 Lettuce 연결 설정 정리

코딩 못하는 감자 2025. 3. 21. 23:59

상황

사용자의 Refresh Token을 저장하는 Redis를 개발 중에

편의상 별도의 보안 처리를 하지 않고 운영해왔다.

최근 비정상적인 외부 접속 로그가 확인되었고,

우선 Redis에 비밀번호 설정부터 적용하기로 했다.

(이후 IP 바인딩, 접근 제한도 예정되어 있음)


⚙️ Redis 설정 - Docker 기준

1. Redis 클라이언트 의존성 (build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-data-red

2. docker-compose.yml 설정

Redis는 Docker 컨테이너로 실행하고 있었기 때문에

비밀번호 설정을 command로 주입해주었다.

redis:
  container_name: test-redis
  image: redis:7.0.10
  ports:
    - "6379:6379"
  networks:
    - medeasy-network
  restart: always
  environment:
    - REDIS_PASSWORD=<password>
  volumes:
    - ./redis-data:/data
    - ./redis.conf:/usr/local/etc/redis/redis.conf  # (선택 사항)
  command:
    - /bin/sh
    - -c
    - redis-server --requirepass "$${REDIS_PASSWORD:?REDIS_PASSWORD variable is not set}"
  • $${REDIS_PASSWORD}: Docker Compose가 아닌 컨테이너 내부에서 환경변수를 치환하기 위해 $$로 사용
  • -requirepass: Redis에 비밀번호를 설정하는 명령어

✅ $${REDIS_PASSWORD} 에 대한 자세한 설명

이 부분은 Docker Compose와 Shell 간의 환경변수 치환 시점 차이 때문에 주의가 필요하다.

📌 설명

  • Docker Compose 파일에서 $는 기본적으로 Docker Compose가 먼저 해석하려고 한다.
  • 하지만 이 설정은 컨테이너 안에서 실행되는 쉘 명령어다.
  • 따라서 $REDIS_PASSWORD가 컨테이너 안에서 해석되기를 원한다면, Docker Compose가 미리 해석하지 않도록 이스케이프(탈출) 시켜야 한다.
  • 그래서 $ 앞에 하나 더 붙여 $$REDIS_PASSWORD 형태로 작성한다.

🧠 예시 비교

작성법 의미

$REDIS_PASSWORD Docker Compose가 실행 전에 치환함 (원치 않음)
$$REDIS_PASSWORD $ 하나가 Compose에 의해 제거되고, 실제 컨테이너 내부에서는 $REDIS_PASSWORD로 인식됨 (정상 작동)

🔐 실제 작동 흐름

  1. Docker Compose가 $${REDIS_PASSWORD}를 $REDIS_PASSWORD로 넘긴다
  2. 컨테이너 내부에서 /bin/sh -c 명령어를 실행할 때 $REDIS_PASSWORD가 치환된다

결과적으로 redis-server --requirepass "mysecret" 형태로 실행됨


🔧 Spring Redis 설정

Redis 연결은 기본적으로 Lettuce 클라이언트를 사용 중이다.

@Value("${redis.jwt.host}")
private String redisJwtHost;

@Value("${redis.jwt.port}")
private int redisJwtPort;

@Value("${redis.jwt.password}")
private String redisJwtPassword;

@Bean(name = "redisConnectionFactoryJwt")
public RedisConnectionFactory redisConnectionFactoryJwt() {
    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisJwtHost, redisJwtPort);
    config.setPassword(redisJwtPassword);

Lettuce 연결 옵션 조정

Redis가 꺼진 상황에서 무한 재시도가 발생하면서

로그인 API가 응답을 주지 않고 멈추는 문제가 있었다.

    SocketOptions socketOptions = SocketOptions.builder()
            .connectTimeout(Duration.ofSeconds(1)) // ⏱️ 연결 시도 제한 시간
            .build();

    ClientOptions clientOptions = ClientOptions.builder()
            .autoReconnect(false) // ❌ 재시도하지 않음
            .socketOptions(socketOptions)
            .build();

    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
            .commandTimeout(Duration.ofSeconds(2))       // ⏱️ 커맨드 실행 타임아웃
            .shutdownTimeout(Duration.ofMillis(100))     // ⏱️ 종료 타임아웃
            .clientOptions(clientOptions)
            .build();

    return new LettuceConnectionFactory(config, clientConfig);
}
  • connectTimeout: Redis 서버가 죽었을 때 빠르게 실패하도록 설정
  • autoReconnect: false: 연결 실패 시 재시도를 막음 (API가 대기 상태로 빠지는 것 방지)

🔐 Refresh Token 저장

Redis는 기본적으로 TTL(Time To Live) 설정이 가능하기 때문에

refresh token의 유효시간을 그대로 Redis에 반영해 저장했다.

try {
    redisJwtTemplate.opsForValue().set(
        userId,
        jwtToken,
        refreshTokenPlusHour, // 유효 시간
        TimeUnit.HOURS
    );
} catch (Exception e) {
    log.info("사용자 {} 로그인 중 redis refresh token 저장 오류 발생: {}", userId, e.getMessage());
}

🧠 실무 팁

  • Docker Redis 환경에서는 password 설정 시 command로 주입하는 것이 가장 직관적이다.
  • 개발 환경이라도 Redis 비밀번호는 꼭 설정해야 한다 Redis는 인증이 없으면 누구나 접속이 가능하다.
  • Lettuce는 기본 재시도 정책이 굉장히 공격적이다. Redis가 죽었을 때 API가 무한 대기 상태에 빠질 수 있으니, 적절한 제한을 두는 것이 좋다.
  • Redis 설정을 /usr/local/etc/redis/redis.conf로 따로 구성하면 IP 바인딩 등도 쉽게 설정 가능하다.