<RedisTemplate 사용법>
https://growth-coder.tistory.com/228
<RedisRepository 사용법>
https://growth-coder.tistory.com/229
<이전 포스팅>
https://growth-coder.tistory.com/227
이전 포스팅에 이어서 이번에는 docker-compose를 통해서 스프링부트 서버 컨테이너, redis 컨테이너 이렇게 컨테이너 두 개를 실행해서 이 둘을 연동해보려고 한다.
<docker 기본 사용법>
https://growth-coder.tistory.com/216
우선 컨테이너 자체는 독립적으로 존재하기 때문에 host에게 오는 요청을 컨테이너가 받기 위해서 host 포트와 컨테이너 포트를 연결했었다. ex) docker run -p [호스트 포트]:[컨테이너 포트] [이미지]
이번 포스팅에서는 스프링 부트 컨테이너와 redis 컨테이너를 띄워서 연동을 해 볼 건데 이러한 과정을 긴 명령어로 실행하려고 하면 힘들 것이다.
그래서 docker-compose 파일에 미리 명령어들을 정의해두고 docker-compose up 명령어 하나로 이러한 과정을 진행해보자.
이번 포스팅의 목표는 다음과 같다.
- 스프링 부트 컨테이너와 redis 컨테이너를 연동한다.
- redis의 db 정보는 컨테이너 내부가 아닌 host에 백업한다.
redis에는 refresh token을 담을 건데 만약 redis 컨테이너를 지웠다가 새로 만들면 모든 유저가 다시 로그인을 해야 할 수도 있기 때문에 host에 백업을 하려고한다.
대략적인 구조는 다음과 같다.
- 스프링 부트 컨테이너의 8080 포트는 host 8080 포트와 연결하여 외부 요청을 받을 수 있도록 한다.
- 스프링 부트 컨테이너와 redis 컨테이너를 같은 네트워크 안에 둔다.
- 2번을 통해 스프링 부트 컨테이너가 redis에 접근할 수 있고 redis의 컨테이너 6379 포트는 host와 연결하지 않는다.
- 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)
https://www.youtube.com/watch?v=EK6iYRCIjYs
'공부 > Spring' 카테고리의 다른 글
[Spring][Android/Kotlin] FCM으로 안드로이드에 푸쉬 알람 보내기 (1) (0) | 2023.08.14 |
---|---|
[Spring] LocalDate, LocalDateTime의 serializer와 deserializer 커스텀하기 (Json과 LocalDate 혹은 LocalDateTime 변환) (0) | 2023.08.07 |
[Spring][Redis] 스프링 부트 RedisRepository 사용법 (0) | 2023.07.15 |
[Spring][Redis] 스프링 부트에서 redis 연동 및 RedisTemplate 사용법 (0) | 2023.07.14 |
[Spring] 스프링 부트 Redis를 사용하여 refresh token 저장하기 (1) (0) | 2023.07.12 |
댓글