이전 장을 보시면 알겠지만, 토큰 발급, 저장, 유효성 검사 등의 로직은 JPA를 통해 MySQL로 구현했습니다. 사실 토큰 자체는 Redis로 구현하려고 했으나, 제반 지식을 익히기엔 시간이 좀 걸리기에 부득이하게 쿼리로 등록하게 됐었네요. 이번 장에서는 JPA로 구현한 토큰 로직을 RedisCache로 수정하는 과정을 수록했습니다.
RDBMS의 관계형데이터베이스 스키마에 구애 받지 않는 데이터베이스의 구조를 통칭합니다.
NoSQL은 일반적으로 테이블 간의 관계에 기반한 DB가 아닌 것들을 통칭하며, 구조와 종류는 매우 많습니다.
사실 NoSQL을 다루는데 있어 RDBMS와의 상세한 비교 및 특징을 세세하게 다루고 싶지만, 분량이 꽤나 방대하므로 일단은 Redis에 대해 간단한 지점만 짚고 넘어가도록 하겠습니다.
레디스(Redis)는 Remote Dictionary Server의 약자로서, "key-value" 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템(DBMS)입니다.
일반적으로 RDBMS는 디스크 기반으로 자료를 저장하지만, Redis는 디스크가 아닌 메모리에 자료를 저장하는게 특징입니다.

Redis는 다음과 같은 자료형을 지원합니다. SNS 등지의 사용자 알고리즘에 입각한 관심 분야 우선순위나, 게임 정보의 랭킹 등에 사용되고 있죠.
spring:
redis:
host: localhost
port: 6379
properties에 다음과 같은 항목을 추가 합니다. 그리고 그 이전에 빌드 디펜던시에 Spring-boot-redis가 없다면 추가 하세요.
@EnableRedisRepositories
@Configuration
public class RedisConfiguration {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// string type 저장시 아래 설정 적용됨.
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
// repository 에서 저장시 hash type으로 저장되므로, 아래 설정 적용됨.
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
return template;
}
}
RedisConfiguration클래스를 생성하고, 컴포넌트 종속성을 선언합니다. 생성자 파라미터에는 application.yml에 등록한 항목들을 참조해 서버 호스트 및 포트 번호를 연결합니다.
저는 스프링 내에서의 redis를 클라이언트로서 Lettuce를 사용 했습니다. 일반적으로 Jedis오 Lettuce를 비교했을 때, 후자가 더 월등히 뛰어나다는게 중론이더군요. 제 경우에는 Jedis가 지원 중단 되었다는 이야기를 들은 바가 있어 고민 없이 Lettuce를 사용해 서버 호스트와 포트 번호를 주입했습니다.
Spring Redis에서는 RedisRepository와 RedisTemplate을 각각 지원합니다. 전자의 경우, 하이버네이트와 같이 스프링 내에서의 객체를 기반으로 자동적으로 RedisHash의 자료구조로 변환하여 영속성을 부여 할 수 있습니다. 후자와 같은 경우에는 개발자가 자료구조를 직접 설정하여 좀 더 세밀하게 관리할 수 있는 특성이 있습니다.
제 경우에는 RedisRepository를 사용했고,RedisTemplate은 종속성 주입의 예시로서 작성 했습니다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RedisHash(value = "access_token", /*TimeToLive = "10"*/)
public class AccessToken implements Serializable {
@Id
private String id;
@Column(name = "email_id")
private String emailid;
@Column(name = "token")
private String token;
@Enumerated(EnumType.STRING)
private TokenType tokenType = TokenType.BEARER;
private boolean revoked;
private boolean expired;
}
기존에 @Entity(name = "access_token")으로 선언했던 클래스를 @RedisHash(value = "access_token")으로 바꾸었습니다. 해당 클래스의 데이터는 RDBMS가 아니라 Redis의 Cache로 다루어지기 때문입니다.
또한 관계형 스키마를 사용할 필요가 없기에, 기존에 조인 컬럼으로 선언했던 Userinfo 변수 역시 지워줬습니다.
또한 직렬화를 해야 하므로(왜인지는 추후 추가) Serialzable을 implements 했습니다.
Id는 UUID에서 String으로 바꾸었고, 해당 파라미터는 객체 생성 시 자동 생성이 되는 대신, 서비스 단위에서 사용자의 UUID를 파라미터로서 String으로 형변환하여 등록하는 것으로 바꾸었습니다.
@RedisHash 어노테이션의 TimeToLive는 해당 자료의 수명을 설정하는 항목 입니다. 이 경우, 직접적인 요청 없이도 Redis 서버 내에서 해당 자료를 자동적으로 지울 수 있다는 편리함이 있습니다.
refreshToken.java는 코드 자체가 거의 다르지 않아 편의상 생략합니다.
public interface AccessTokenRepository extends CrudRepository<AccessToken, String> {
List<AccessToken> findAllValidTokenByUser(String id);
Optional<AccessToken> findByToken(String token);
}
데이터 접근 계층을 담당하는 인터페이스에서는 JpaRepository<K,V>를 CrudRepository<K,V>로 대신 상속합니다. 기존의 네이티브 쿼리 역시 MySQL에서 사용하는 쿼리이므로 수정합니다. RedisHash는 기본적으로 String으로 파라미터를 받으므로, 해당 리포지토리의 식별자로 UUID 대신 String을 사용합니다.
public class AuthenticationService {
...
private void revokeAllUserTokens(Userinfo user) {
var validUserTokens = tokenRepository.findById(user.getId().toString())
.orElseThrow(null);
var validRefreshTokens = refreshRepository.findById(user.getId().toString())
.orElseThrow(null);
tokenRepository.delete(validUserTokens);
refreshRepository.delete(validRefreshTokens);
}
...
}
revokeAllUserTokens의 내부 로직 역시 수정합니다. JPA에서 Redis로 바꾼 만큼 좀 더 간소화 됐습니다.

1. 테스트에 앞서 redis-server start로 레디스 서버를 실행 합니다. 실행 되면, redis-cli를 타이핑 하여 클라이언트를 실행 시킵니다.

2. 웹 어플리케이션을 실행 한 뒤, POSTMAN에 등록 된 기존의 API를 테스트 합니다.

3. redis-cli에서 해당 키 값을 조회하여 자료구조가 등록됐는지 확인하고, 저장된 토큰 값을 조회합니다.

4. POSTMAN에 응답된 토큰 값과 비교. jwt.io에서도 유효성이 확인되는 것을 볼 수 있습니다.
비문, 오탈자와 코드 오류 및 잘못된 지식에 대한 지적 및 질문은 언제나 환영합니다.