[table-now] Refresh Token 저장소를 Redis로 교체하며 인증 구조 고도화하기

2025. 4. 21. 22:02·Dev Projects

🔍 개요

JWT 기반 인증 시스템을 운영하면서 Refresh Token 저장소를 기존 RDB(MySQL)에서 Redis로 전환하였습니다.
이번 작업은 성능 향상뿐 아니라, 인증 시스템의 Stateless 구조를 더욱 명확히 구현하는 과정이었습니다.

 


 

📈 성능 개선 / 코드 개선 요약

  • Refresh Token을 Redis 기반 Key-Value 구조로 전환
  • 인증 흐름에서 발생하는 불필요한 DB 접근 제거
  • TTL 기반 자동 만료와 빠른 조회로 인증 시스템 성능 최적화

 


 

🧩 문제 정의

기존에는 Refresh Token을 RDB에 저장하는 방식으로 구현되어 있었습니다. 하지만 이 방식은 다음과 같은 문제를 갖고 있었습니다:

 

  • RDB 부하 증가: 로그인 시 INSERT, 재로그인 시 UPDATE, 재발급 시 SELECT 쿼리 발생
  • I/O 병목: 트래픽이 급증할 경우 DB 성능 저하 가능성
  • 수동 만료 처리: expiredAt 필드를 매번 비교해야 했음

 


 

💡 가설

Redis 기반 저장소로 전환하면 다음과 같은 이점을 기대할 수 있습니다:

 

  • 인메모리 기반의 빠른 읽기/쓰기 성능 확보
  • TTL(Time To Live)을 통한 자동 만료 및 데이터 정리
  • Redis Key 삭제를 통한 간편한 무효화 처리
  • 클러스터링/샤딩을 통한 수평 확장 구조에 최적화

 


 

🛠️ 해결 방법 & 기술 선택

🔸 왜 Redis인가?

  • 성능 향상
        - RDB는 매 로그인 시 INSERT/UPDATE, 토큰 검증 시 SELECT 쿼리 필요
        - Redis는 인메모리 기반이므로, Refresh Token 검증과 저장이 빠름
  • 확장성
        - Redis는 수평 확장이 가능한 구조 (클러스터링, 샤딩 지원)
        - 분산 서버 환경에서 중앙 집중식 토큰 저장소로 활용하기 적합
  • 간편한 만료 처리
        - 기존 RDB에서는 `expiredAt`을 수동으로 비교해야 했지만,
        - Redis에서는 TTL 설정만으로 자동 만료 및 데이터 정리 가능
  • Stateless 인증 구조에 최적화
        - JWT 자체는 Stateless하지만, Refresh Token을 저장하려면 서버 상태를 일부 관리해야 함
        - Redis는 상태 저장 공간으로 최소한의 정보를 빠르게 관리하는 데 적합
        - 로그아웃 시 Redis 키 삭제로 바로 토큰 무효화 가능
항목 RDB Redis
저장소 구조 MySQL In-memory Key-Value
토큰 조회 `findByToken()` `get()`
만료 처리 `expiredAt` 수동 비교 TTL 자동 삭제
로그아웃 처리 `delete` Key 삭제
동시 로그인 1개 제한 기기별 여러 개 허용
(로그인 시 리프레시 토큰 발급 / 계정당 제한 두지 않음)
성능 I/O 비용 존재 빠른 응답 속도

 

🔸 RedisTemplate 사용 이유

항목 `@RedisHash` + `CrudRepository` `RedisTemplate`
구조 해시(`HSET`) 기반 단순 Key-Value(`SET`)
TTL 관리 전체 객체 기준 TTL
(`@RedisHash(timeToLive=604800)`)
Key 단위 TTL 설정 가능
키 생성 자동
(`refreshToken:{@Id}`)
직접 명시 가능
(`refreshToken:{token}`)

Refresh Token은 단일 값(유저ID)만 저장하고 TTL로 만료 시간을 관리할 때는, Hash 구조를 사용할 필요가 없다.
→ Redis의 Key-Value를 직접 사용 (RedisTemplate `opsForValue().set()`, `get()`, `delete()`사용)

 


 

🧱 구현 내용

 

🔸 엔티티 구조 변경

⛔ 변경 전 (RDB)

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RefreshToken extends TimeStamped {

    private static final long EXPIRATION_DAYS = 7; // 만료 시간 7일

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private Long userId;

    @Column(nullable = false)
    private String token;

    @Column(nullable = false)
    private LocalDateTime expiredAt;
    
    // 생성자 등 생략
}


✅ 변경 후 (DTO) → 단순 데이터 전달

@Builder
@Getter
public class RefreshToken {

    private String token;
    private Long userId;
}
  • 데이터 저장/삭제는 `TokenService`에서 `RedisTemplate`으로 명시적으로 수행
  • `@Entity`, `@RedisHash`, `CrudRepository`를 사용하지 않음
  • Redis 저장소의 리프레시 토큰:  TTL을 명시
  • Cookie의 리프레시 토큰: setMaxAge
  • → DTO에서 `expiredAt`을 따로 관리할 필요가 없음
  • → 토큰 값과 userId만 담아서 전달하는 역할

 

🔸 서비스 로직 변경

  • 기존 RefreshTokenRepository 제거 → RedisTemplate<String, String> 사용
  • 토큰 저장: opsForValue().set(key, value, ttl)
  • 토큰 삭제: delete(key)

 

https://github.com/spring-team-7/table-now/pull/111

 

[refactor] RefreshToken 저장 방식 Redis로 전환 및 테스트 개선 by mannaKim · Pull Request #111 · spring-team-7/tab

🔗 Issue Number close #101 📝 작업 내역 RefreshToken 저장소를 RDB에서 Redis로 변경 Redis TTL 설정을 통해 리프레시 토큰 자동 만료 처리 적용 로그아웃 시 Redis에서 토큰 삭제 처리 구현 불필요한 @Transactio

github.com

 


 

🎉 결과 및 효과

  • 로그인 응답 시간 단축 (DB 접근 제거)
  • Refresh Token 만료 로직 단순화 및 안정성 확보
  • Stateless 인증 구조에 맞는 저장소 구성 달성
  • 다중 기기 로그인 대응 가능 (토큰 중복 저장 가능)

 



🪞 회고 & 개선 방향

배운 점

  • JWT 기반 인증 흐름과 RefreshToken 관리의 실제 운영 관점 이해
  • Redis TTL, Key 관리 방식에 대한 실전 경험
  • 인증 시스템 설계에서 보안성과 성능을 동시에 고려해야 한다는 교훈

고민했던 점

  • @RedisHash를 사용할지, RedisTemplate으로 명시적으로 관리할지 → TTL 설정과 관리 유연성 때문에 후자 선택
  • 다중 로그인 정책 적용 시 userId 기반 저장보다 token 기반 저장이 더 유연하다는 점 고려

다음 개선 목표

  • Refresh Token 저장 방식 외에도 AccessToken 블랙리스트 처리 기능 적용 예정
  • Redis 기반 세션/토큰 관리 전반을 고도화하여 보안성 향상 도모

 


 

🏁 마무리

이번 리팩토링을 통해 RDB 기반 인증 시스템에서 발생하는 병목 요소를 제거하고, 더 나은 성능과 확장성을 확보할 수 있었습니다.
JWT의 Stateless 특성을 살리면서도 필요한 상태 정보(RefreshToken)를 가볍고 효율적인 Redis로 전환하여 구조적 일관성도 유지할 수 있었습니다.

 

저작자표시 비영리 변경금지 (새창열림)

'Dev Projects' 카테고리의 다른 글

[table-now] S3 이미지 업로드 - Presigned Url 방식으로 구현하기  (1) 2025.05.23
[table-now] AccessToken 블랙리스트 기반 토큰 무효화 기능 구현  (0) 2025.04.22
[table-now] 소셜 로그인(Kakao, Naver) 통합 구현기  (1) 2025.04.21
[Spring Data JPA_일정 관리 앱 Develop] 필수 & 도전 기능 구현 기록 🖋️  (1) 2025.02.13
[Spring JDBC_일정 관리 앱 만들기] 회고 및 트러블 슈팅  (0) 2025.02.04
'Dev Projects' 카테고리의 다른 글
  • [table-now] S3 이미지 업로드 - Presigned Url 방식으로 구현하기
  • [table-now] AccessToken 블랙리스트 기반 토큰 무효화 기능 구현
  • [table-now] 소셜 로그인(Kakao, Naver) 통합 구현기
  • [Spring Data JPA_일정 관리 앱 Develop] 필수 & 도전 기능 구현 기록 🖋️
기만나🐸
기만나🐸
공부한 내용을 기록합시다 🔥🔥🔥
  • 기만나🐸
    기만나의 공부 기록 🤓
    기만나🐸
  • 전체
    오늘
    어제
    • ALL (147) N
      • TIL (Today I Learned) (56) N
      • Dev Projects (15)
      • Algorithm Solving (67)
        • Java (52)
        • SQL (15)
      • Certifications (8)
        • 정보처리기사 실기 (8)
  • 인기 글

  • 태그

    Firebase
    시뮬레이션
    programmers
    백트래킹
    jQuery
    백준
    websocket
    jwt
    bootstrap
    프로그래머스
    javascript
    BOJ
    sql
    CSS
    jpa
    Google Fonts
    HTML
    완전탐색
    dp
    join
    Subquery
    greedy
    다이나믹프로그래밍
    java
    BFS
    자료구조
    DFS
    그리디
    GROUP BY
    mysql
  • 최근 글

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
기만나🐸
[table-now] Refresh Token 저장소를 Redis로 교체하며 인증 구조 고도화하기
상단으로

티스토리툴바