이제 Redis를 사용해보도록 하자.
설치는 아래 블로그를 참고해서 설치하였다.
윈도우(Window)에 레디스(Redis) 설치 및 확인하기
https://nazzang19.tistory.com/138
스프링 부트에서 Redis를 사용하기 위해 build.gradle에 의존성을 추가해준다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
application.yml에 사용할 Redis의 호스트와 포트를 지정해준다.
로컬에서 사용한다면 localhost, 다른 서버나 도커 등을 사용한다면 그에 맞는 호스트로 설정한다. 디폴트 포트 번호는 6379 이다.
spring:
redis:
host: localhost
port: 6379
마지막으로 애플리케이션에서 Redis와 연동하기 위해 RedisConfig 파일을 만들어서 별도의 값을 세팅하고 빈에 등록한다.
하지만 Spring boot 2.0부터는 의존성만 추가해주어도 RedisTemplate와 String Template을 자동으로 Bean을 생성해주기 때문에, 따로 RedisConfig 파일을 만들어 Bean에 등록해주지 않아도 바로 주입해서 사용 가능하다.
하지만 자동 등록되는 RedisTemplate은 기본적으로 JDK 직렬화(serializer)를 사용하는데, 이는 Redis 바이너리 데이터(\xac\xed\x00...)로 저장되므로, 사람이 읽기 어렵고 호환성이 떨어진다.
따라서 StringRedisSerializer를 설정해주어서 이를 해결한다.
설정하지 않는다면 redis-cli로 데이터를 확인하는데 있어서 어려움이 있다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/* Springboot 2.0부터는 RedisTemplate와 StringRedisTemplate를 자동으로 Bean을 생성해주기 때문에,
* 따로 Bean 등록을 안해줘도 직접 주입해서 사용 가능. 하지만 자동 등록되는 RedisTemplate은 기본적으로 JDK 직렬화(Serializer)를
* 사용하는데, 이는 Redis에 바이너리 데이터(\xac\xed\x00...)로 저장되므로, 사람이 읽기 어렵고 호환성이 떨어진다.
* 따라서 StringRedisSerializer를 설정해주어서 이를 해결한다.
* 직렬화 설정을 하기 위해서는 RedisConfig가 필요하다.
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 일반적인 Key:Value의 경우에 사용되는 문자열(String) 직렬화
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
// Hash를 사용할 경우에 사용되는 문자열(String) 직렬화
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
위 설정을 추가하면 Redis에 저장된 데이터를 사람이 읽을 수 있는 String 형태로 관리 가능하다.
Redis를 사용하는 방법에는 두 가지 방법이 있다.
@RedisHash)를 만들어서 Redis에 저장하기RedisTemplate를 사용하여 저장하기 (추천)이 방법은 엔티티(@RedisHash) 객체를 만들어서 직접 Redis에 저장하는 방법이다.
import jakarta.persistence.Id;
import lombok.*;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.index.Indexed;
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
@RedisHash(value = "refresh_token") //Redis에 저장될 객체를 정의할떄 사용. @Entity와 유사한 개념이며, Redis에서 데이터를 Hash 형태로 저장한다.
public class RefreshToken {
@Id
private String userName;
@Indexed //Redis에서 특정 필드를 기준으로 데이터를 빠르게 검색할 수 있도록 해준다. 이걸 사용하면 필드가 인덱싱되어 쿼리기반검색(findBy...)가 가능해진다.
//StringRedisTemplate를 사용하면 @Indexed 없이도 특정 필드(token)로 검색이 가능하다.
private String token;
private String role;
@TimeToLive
private long ttl;
public RefreshToken update(String token, long ttl){
this.token = token;
this.ttl = ttl;
return this;
}
}
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
//Redis는 JpaRepository 대신 CrudRepository를 상속받아서 사용한다.
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
Optional<RefreshToken> findByToken(String token);
Optional<RefreshToken> findByAuthId(String authId);
}
Redis는 JpaRepository 대신 CrudRepository를 상속받아서 사용한다.
마찬가지로 findBy~ 구문을 사용할 수 있다.
import org.springframework.stereotype.Service;
@Service
public class RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepository;
public RefreshTokenService(RefreshTokenRepository refreshTokenRepository) {
this.refreshTokenRepository = refreshTokenRepository;
}
public void saveRefreshToken(String username, String refreshToken) {
refreshTokenRepository.save(new RefreshToken(username, refreshToken));
}
public void deleteRefreshToken(String username) {
refreshTokenRepository.deleteById(username);
}
public RefreshToken getRefreshToken(String username) {
return refreshTokenRepository.findById(username).orElse(null);
}
}
이 방식은 엔티티를 사용하지 않고 직접 key-value 형식으로 저장하는 방식이다.
Spring Data Redis에서 제공하는 Redis 조작을 위한 주요 인터페이스이다.
이를 사용하면 Redis에 데이터를 저장, 조회, 삭제할 수 있으며, 다양한 Redis 자료구조(String, Hash, List, Set, ZSet)을 지원한다.
Redis의 기본 명령어(SET, GET, DEL, HSET 등)를 Java 코드로 쉽게 다룰 수 있도록 도와준다.
다양한 Redis 자료구조를 지원한다.
opsForValue() → String 저장 (Key-Value)opsForHash() → Hash 저장opsForList() → List 저장opsForSet() → Set 저장opsForZSet() → Sorted Set 저장Redis 직렬화/역직렬화 기능 제공
Json, String, Binary 등 다양한 직렬화 방식을 지원한다.import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
//refresh token 저장 (14일 만료)
public void saveRefreshToken(String key, String refreshToken){
redisTemplate.opsForValue().set("refresh save:" + key, refreshToken, 14, TimeUnit.DAYS);
}
//refresh token 조회
public String getRefreshToken(String key){
return (String) redisTemplate.opsForValue().get("refresh get:" + key);
}
//refresh token 삭제 (로그아웃 시)
public void deleteRefreshToken(String key) {
redisTemplate.delete("refresh:" + key);
}
}
RedisTemplate을 사용하면, Entity와 CrudRepository를 상속할 필요 없이, RedisTemplate 만으로 CRUD 작업이 가능하다.
따라서, 나는 RedisTemplate을 사용하여 RefreshToken을 Redis에 저장하려고 한다.
내 프로젝트에서는 Access Token만을 생성하도록 해놓았어서, Refresh Token을 Redis에 저장하기 위해서는, 내 프로젝트를 조금 수정할 필요가 있었다.
Access Token 생성 관련해서는, 이전 JWT 포스팅을 참고바란다.

yml파일의 Jwt 항목에서 Refresh 토큰을 위한 유효기간을 작성해주었다.

yml에 추가한 Refresh 토큰 유효기간을 주입해준다.

Refresh Token을 생성하는 로직을 추가해 주었다.

새로 작성한 RedisService를 생성자 주입해준다.

로그인 할 때, Refresh Token도 같이 생성하여서 Redis에 저장해주는 코드를 추가하였다.

로그인을 수행하면, Access Token과 Refresh Token이 잘 생성된 것을 확인할 수 있다.
참고자료
https://bcp0109.tistory.com/328
https://developer-nyong.tistory.com/21
https://westmino.tistory.com/157