본문 바로가기
공부/Spring

[Spring] 스프링 부트 Redis를 사용하여 refresh token 저장하기 (2) - docker-compose 사용법

by 웅대 2023. 7. 17.
728x90
반응형

<RedisTemplate 사용법>

https://growth-coder.tistory.com/228

 

[Spring][Redis] 스프링 부트에서 redis 연동 및 RedisTemplate 사용법

이번 포스팅에서는 스프링 부트에서 redis와 연동하고 값을 저장해보려고 한다. 먼저 스프링 부트에 redis 관련된 의존성을 추가해준다. 만약 기존 진행 중인 프로젝트에 적용한다면 build.gradle에

growth-coder.tistory.com

<RedisRepository 사용법>

https://growth-coder.tistory.com/229

 

<이전 포스팅>

https://growth-coder.tistory.com/227

 

[Spring] 스프링 부트 Redis를 사용하여 refresh token 저장하기 (1)

지금까지 인증 및 인가를 구현할 때 Access token만 사용했었는데 Access token의 한계를 느껴서 Refresh token을 적용해보려고 한다. Access token 우선 Access token을 사용할 때의 단점은 다음과 같다. 1. 토큰이

growth-coder.tistory.com

이전 포스팅에 이어서 이번에는 docker-compose를 통해서 스프링부트 서버 컨테이너, redis 컨테이너 이렇게 컨테이너 두 개를 실행해서 이 둘을 연동해보려고 한다.

 

<docker 기본 사용법>

https://growth-coder.tistory.com/216

 

[Docker] 도커 개념 및 사용법

프로젝트를 진행하면서 로컬에서 잘 작동하던 프로젝트가 서버에 배포하면 잘 작동하지 않을 수 있다. 수많은 원인이 존재하겠지만 라이브러리 버전이 다르다거나 네트워크 환경이 다르다거나

growth-coder.tistory.com

우선 컨테이너 자체는 독립적으로 존재하기 때문에 host에게 오는 요청을 컨테이너가 받기 위해서 host 포트와 컨테이너 포트를 연결했었다. ex) docker run -p [호스트 포트]:[컨테이너 포트] [이미지]

 

이번 포스팅에서는 스프링 부트 컨테이너와 redis 컨테이너를 띄워서 연동을 해 볼 건데 이러한 과정을 긴 명령어로 실행하려고 하면 힘들 것이다.

 

그래서 docker-compose 파일에 미리 명령어들을 정의해두고 docker-compose up 명령어 하나로 이러한 과정을 진행해보자.

 

이번 포스팅의 목표는 다음과 같다.

  1. 스프링 부트 컨테이너와 redis 컨테이너를 연동한다.
  2. redis의 db 정보는 컨테이너 내부가 아닌 host에 백업한다.

redis에는 refresh token을 담을 건데 만약 redis 컨테이너를 지웠다가 새로 만들면 모든 유저가 다시 로그인을 해야 할 수도 있기 때문에 host에 백업을 하려고한다.

 

대략적인 구조는 다음과 같다.

  1. 스프링 부트 컨테이너의 8080 포트는 host 8080 포트와 연결하여 외부 요청을 받을 수 있도록 한다.
  2. 스프링 부트 컨테이너와 redis 컨테이너를 같은 네트워크 안에 둔다.
  3. 2번을 통해 스프링 부트 컨테이너가 redis에 접근할 수 있고 redis의 컨테이너 6379 포트는 host와 연결하지 않는다.
  4. redis 설정을 통해 컨테이너 외부 호스트에 백업을 하고 외부 호스트의 설정 파일을 통해 redis 설정을 한다.

redis.conf 설정

redis.conf를 호스트, 즉 내 컴퓨터 내부에 생성해서 redis를 설정한다.

 

위에서 보다싶이 redis 컨테이너 내부 conf 파일이 아닌 내 컴퓨터 conf 파일을 사용하도록 설정할 것이다.

 

<redis.conf>

bind 0.0.0.0 # 외부 호스트 요청 수락
port 6379 # 컨테이너 내부 port 번호 6379 사용
save 900 1 # 900초 동안 1개의 값 변경 시 백업
requirepass 1234 # 비밀번호 1234

 

스프링 부트 설정

우선 application.properties에 redis 연동을 위한 정보와 jwt에 관련된 정보가 담겨야 한다.

 

<application.properties>

spring.redis.host=${REDIS_HOST}
spring.redis.port=${REDIS_PORT}
spring.redis.password=${REDIS_PASSWORD}
jwt.secretKey=secret
jwt.expiredMs=600000

${}로 감싸있는 부분은 환경 변수의 내용을 가져온다는 뜻이다.

 

이 환경 변수는 docker-compose.yml에 등록할 예정이다.

 

그 다음은 RedisConfiguration 클래스이다. RedisRepository를 사용할 예정이므로 @EnableRedisRepositories 어노테이션을 달아준다.

 

<RedisConfiguration>

@Configuration
@EnableRedisRepositories
public class RedisConfiguration {
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private int redisPort;
    @Value("${spring.redis.password}")
    private String redisPassword;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
        config.setPassword(redisPassword);
        return new LettuceConnectionFactory(
                config
        );
    }

    @Bean
    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

RedisConnectionFactory와 RedisTemplate을 세팅해준다. 자세한 설명은 가장 위의 RedisTemplate 사용법을 참고하면 된다.

 

다음은 Redis에 저장할 Token 엔티티이다.

 

<Token>

@RedisHash("token")
@AllArgsConstructor
@ToString
public class Token {
    @Id
    private Long id;
    private String refreshToken;
    @TimeToLive
    private Long expiration;
}

id는 RedisHash와 함께 key 값이 될테고 실질적인 데이터는 refreshToken이다.

 

refresh token을 생성할 때 redis에 저장할 예정이다.

 

그리고 오랫동안 접속하지 않으면 refresh token을 삭제할 예정이므로 @TimeToLive 어노테이션을 통해 만료 기간을 정해준다.

 

만약 모든 인스턴스의 만료 기간이 동일하다면 다음과 같이 @RedisHash에 TTL을 설정해도 된다.

@RedisHash(value="token", timeToLive = 10)
@AllArgsConstructor
@ToString
public class Token {
    @Id
    private Long id;
    private String refreshToken;
}

refresh token이 삭제 된다면 사용자는 다시 로그인을 해야 한다.

 

다음은 TokenRepository이다.

 

<TokenRepository>

@Repository
public interface TokenRepository extends CrudRepository<Token, Long> {
    
}

저장 메소드만 사용할 예정이라 따로 메소드는 작성하지 않아도 된다.

 

다음은 JwtUtil 클래스이다.

 

일단은 refresh token을 생성하는 메소드만 만들었다. 이 메소드 안에는 RedisRepository를 통해 refresh token을 redis에 저장하는 과정이 포함되어 있다.

 

<JwtUtil>

@Component
@RequiredArgsConstructor
public class JwtUtil {
    private final TokenRepository tokenRepository;
    @Value("${jwt.secretKey}") //application.properties에 저장되어 있는 값을 가져온다.
    private String secretKey;
    @Value("${jwt.expiredMs}") //application.properties에 저장되어 있는 값을 가져온다.
    private Long expiredMs;

    public String createRefreshToken(Long id) {
        Claims claims = Jwts.claims();
        claims.put("id", id);

        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiredMs))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
                
        System.out.println("token = " + token);
        Token token1 = new Token(id, token,10L);
        tokenRepository.save(token1);
        return token;
    }
}

token을 보면 expiration을 10L로 설정해두었다. 이 필드는 @TimeToLive 어노테이션이 달려있고 10초가 지나면 해당 key-value 쌍을 삭제한다는 뜻이다.

 

다음은 TokenController이다. 유저 pk를 path variable로 받아서 refresh token을 생성 후 redis에 저장한다.

@RestController
@RequiredArgsConstructor
public class RedisController {
    private final JwtUtil jwtUtil;
    @GetMapping("refresh/{id}")
    public String getRefresh(@PathVariable("id") Long id) {
        System.out.println("id = " + id+id);
        String refreshToken = jwtUtil.createRefreshToken(id);
        return refreshToken;
    }
}

Dockerfile 설정

FROM openjdk:19-alpine
ARG JAR_PATH=build/libs/*.jar
COPY ${JAR_PATH} /home/server.jar
ENTRYPOINT ["java","-jar","/home/server.jar"]

dockerfile은 확장자 없이 프로젝트 루트 경로에 생성하면 된다.

 

컨테이너를 실행할 때 자동으로 jar 파일을 복사해서 실행하는 코드이다.

 

docker-compose.yml 설정

Dockerfile과 같은 프로젝트 루트 위치에 생성한다.

 

<docker-compose.yml>

version: '3.4'
services:
  redis:
    image: redis
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - C:\Users\growth\redis_volume\redis.conf:/usr/local/etc/redis/redis.conf
      - C:\Users\growth\redis_volume\data:/data
    restart: always
  springboot:
    image: openjdk:19-alpine
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - 8080:8080
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_PASSWORD: 1234
    depends_on:
      - redis

우선 services 아래로 redis와 springboot가 있는 모습을 볼 수 있는데 이는 redis 컨테이너와 springboot 컨테이너가 같은 네트워크로 연결되어 실행한다는 뜻이다.

 

redis 부터 보자.

  redis:
    image: redis
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - C:\Users\growth\redis_volume\redis.conf:/usr/local/etc/redis/redis.conf
      - C:\Users\growth\redis_volume\data:/data
    restart: always

redis 이미지를 사용하고 redis-server /usr/local/etc/redis/redis.conf 명령어를 실행한다.

 

구조 그림을 다시 보면

백업 파일과 설정 파일을 컨테이너 내부의 파일을 사용하는 것이 아닌 호스트, 즉 내 컴퓨터에 있는 파일을 사용하도록 설정을 해야 한다. (새로운 컨테이너를 만들어도 데이터를 유지하기 위함)

 

이를 위해 volumes라는 지시어를 통해  호스트 파일 경로와 컨테이너 내부 파일 경로를 매핑해주면 된다.

 

[호스트 파일 경로] : [컨테이너 내부 파일 경로]

 

또 restart: always를 통해 컨테이너가 멈추면 다시 시작하도록 설정한다.

 

다음은 springboot를 보자.

 

  springboot:
    image: openjdk:19-alpine
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_PASSWORD: 1234
    depends_on:
      - redis

이미지는 openjdk의 19-alpine 태그를 사용한다.

 

build 하위 context는 Docker 파일의 위치, build 하위 dockerfile은 Dockerfile의 이름이다.

 

프로젝트 루트 위치 Dockerfile을 생성했으므로 경로는 .이다.

 

그리고 호스트 포트와 컨테이너 내부 포트도 매핑해주자.

 

[ 호스트 포트 ] : [ 컨테이너 내부 포트 ]

 

그 다음 environment는 환경 변수이다.

 

application.properties에서 ${}로 감싼 값은 환경 변수라고 했다.

 

이 값을 넣어준다고 보면 된다. 호스트 이름, 포트 번호, 비밀번호를 입력해준다.

 

servies로 redis와 springboot가 묶여있어서 같은 네트워크에 있다고 볼 수 있다.

 

그래서 springboot에 설정해야하는 호스트 이름을 localhost나 ip 주소가 아닌 redis로 간편하게 설정할 수 있다.

 

그리고 springboot 컨테이너는 redis 컨테이너가 실행하기 전에 실행되면 안되기 때문에 depends_on 지시어를 통해 redis컨테이너가 실행된 이후 springboot 컨테이너가 실행되도록 설정한다.

 

이제 준비가 다 되었다. bootjar를 통해 jar 파일을 생성하고 다음 명령어를 입력하면 된다.

 

docker-compose -f "docker-compose.yml" up -d --build

 

-f는 docker-compose.yml 파일의 경로 및 이름을 의미한다.

 

나는 프로젝트 루트에 docker-compose.yml 파일을 생성했기 때문에 -f "docker-compose.yml" 옵션을 추가하지 않아도 된다.

 

만약 경로나 이름이 다르다면 -f 옵션을 통해 설정해준다.

 

-d 옵션을 통해 백그라운드로 실행한다.

 

--build 옵션을 통해 컨테이너 실행 전에 이미지를 빌드한다.

 

두 컨테이너가 성공적으로 실행되었다.

 

이제 스프링 부트 Controller에 지정했던 경로로 요청을 보내보자.

 

refresh token이 반환되었다. 이제 redis-cli를 통해 해당 token:1 키가 생성되었는지 확인하자.

 

docker ps 명령어를 통해 redis container의 이름을 확인 후 다음 명령어를 입력한다.

 

docker exec -it [redis 컨테이너 이름] redis-cli

 

여기서 keys * 명령어를 입력하면 다음과 같이 NOAUTH Authentication required 에러가 발생할 것이다.

이는 redis.conf 파일에 비밀번호를 설정했기 때문이다.

 

auth [비밀번호] 명령어를 통해 인증 후 다시 keys * 명령어를 입력한다.

"token:1" key가 성공적으로 저장되었다.

 

TimeToLive 속성을 통해 10초가 지나면 삭제가 되도록 설정했는데 10초 후에 다시 keys * 명령어를 입력해보자.

시간이 지나 사라졌다.

 

원하는 refresh token의 유효 기간에 따라 TimeToLive 어노테이션이 달린 필드의 값을 정해주면 된다.

 

참고

Docker compose로 Redis 실행하기 (velog.io)

 

Docker compose로 Redis 실행하기

Docker로 쉽게 Redis를 구동시키자!

velog.io

https://www.youtube.com/watch?v=EK6iYRCIjYs 

 

728x90
반응형

댓글