Spring Boot + JWT + Redis 적용해보기

Seokjun Moon·2023년 11월 24일
0

JWT

기본이라고 할 수 있죠. 카테캠에서는 프론트에서 쿠키로 구현할 시간이 부족하다고 하여 이전처럼 local storage를 이용하기로 했는데, 이번 see-realview 프로젝트는 시간이 널널하기 때문에 저장소 선택을 고민했습니다.

이건 다시 프론트와 조율해야 할 점이라서, 백엔드에서 토큰을 저장할 위치를 고민했습니다.

JWT

우선 Redis를 사용합니다! 이전에는 JpaRepository로 구현하여 요청마다 유저 검증을 위한 DB 조회가 최소 2번 발생했습니다.

  1. UserDetails에서 user repository 조회 1회
  2. 토큰 검증을 위한 token repository 조회 1회

매 요청마다 유저 관련한 조회가 2번 발생하기 때문에, 이를 줄일 필요가 있었고 토큰 정보들은 Reids를 이용하여 보다 빠르게 접근하도록 변경하였습니다.

이미지 파싱

문제는 또 발생합니다. 이미지 OCR 과정이 아무리 성능을 높이긴 해도 1초라는 긴 시간이 필요하기 때문에, 이를 더 줄이고자 자주 OCR 요청이 될 것 같은 이미지들은 데이터베이스에 저장하여 빠르게 결과를 알 수 있도록 설계를 수정했습니다. 하지만 사진이 많아질수록, 데이터베이스에서 사진을 조회하는 시간이 오래 걸립니다.

이걸 방지하기 위해,

  1. 자주 사용하는 이미지는 Redis에 저장하여 빠르게 접근하고,
  2. Redis에서 찾지 못할 경우에는 MySQL 데이터베이스에 접근하여 검색
  3. 여기에서도 찾지 못하면 OCR 검색

이렇게 설계했습니다. 따라서 Redis를 도입했습니다!

Redis를 사용해보자!

초간단! 도커로 redis 컨테이너 띄우고...!!

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

의존성 추가 후

@Configuration
public class RedisConfig {

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

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


    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());

        return objectMapper;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate() {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        return redisTemplate;
    }
}

이렇게 템플릿 추가하면 완료!!! JPA를 이용해서 구현도 가능하지만, 사실 그런 기능 필요 없이 유저 id로 토큰 조회랑 이미지 주소로 검색 뿐이기도 하고.. 데이터베이스도 JDBC로 먼저 사용해본 뒤에 다른 기술들을 사용해서 해봤기 때문에 이것 또한 일단은! 기본 먼저 사용해보기로 .. 기타 등등의 이유도 있지만 뭐! 기본이 중요하자나?!

왠 ObjectMapper

StringRedisSerializer 을 사용하기 때문에 ..! 그럼 왜 String을 ??

  1. Class Type을 별도로 지정할 필요가 없고
  2. Package정보를 포함하지 않아도 되며
  3. 용량을 최소화할 수 있다

GenericJackson2Json, Jackson2Json은 역직렬화(?)랑 클래스 타입 문제가 발생해서 나중에 에러를 종잡을 수 없다고 하기 때문에 일단은 안전하게 String으로 진행.

저장, 조회하기

@Override
public Optional<Token> findTokenById(Long id) {
    String key = getKeyById(id);
    String token = valueOperations.get(key);

    if (token == null) {
        return Optional.empty();
    }

    try {
        return Optional.ofNullable(objectMapper.readValue(token, Token.class));
    }
    catch (JsonProcessingException e) {
        throw new ServerException(ExceptionStatus.TOKEN_PARSING_ERROR);
    }
}

@Override
public void save(Long id, Token token) {
    try {
        String key = getKeyById(id);
        String value = objectMapper.writeValueAsString(token);
        valueOperations.set(key, value);
    }
    catch (JsonProcessingException e) {
        throw new ServerException(ExceptionStatus.TOKEN_PARSING_ERROR);
    }
}

private static String getKeyById(Long id) {
    return TOKEN_PREFIX + id;
}

이렇게 objectMapper를 이용하면 끝!!

나중에 이미지 데이터도 들어가야 하기 때문에 prefix를 두어 구분하도록 설계했다.>!! 그래야 구분하기 쉽고 아마 redis도 인덱스를 설정할 수 있을 것 같은데... 그럴 때 편하려고 해두었다!

열흘만에 쓰는 글 .......
정리하고 싶은 내용들이 산더미지만
과제 .. 행사 .... 멈춰줘 ........

profile
차근차근 천천히

0개의 댓글