[Redis #2] JWT 인증 시스템에서 Redis 활용하기
Refresh Token 보안 구조 설계와 구현
1. 들어가며
JWT 기반 인증 시스템에서 Refresh Token 관리는 보안성과 사용자 경험의 핵심 요소입니다.
특히 다중 인스턴스 환경이나 서버 측 토큰 제어가 필요한 경우, 토큰 저장소의 도입이 중요합니다.
Redis를 활용한 화이트리스트 방식 Refresh Token 관리 구조와 Spring Boot로의 실제 구현을 설명합니다.
2. 왜 서버가 토큰을 저장해야 할까?
JWT는 기본적으로 Stateless하며, 서버는 토큰 검증만 수행합니다.
하지만 다음과 같은 요구사항이 있다면 서버가 토큰을 저장해야 합니다:
- 강제 로그아웃, 계정 정지, 토큰 재사용 방지
- 1인 1토큰 정책 (중복 로그인 방지)
- 로그아웃 후 토큰 무효화
- Rotation 기반 보안 강화
→ Refresh Token 저장소가 필요하고 Redis가 적합
3. 왜 Redis인가? 다른 대안들과 비교
Redis의 장점
- 메모리 기반 → 빠른 성능
- TTL 지원 → 자동 만료 처리
- 다중 인스턴스 간 공유 가능
- 키 검색(SCAN), 실시간 모니터링
비교 표
항목 | Redis | DB 저장 (RDB) | JVM Memory (Map 등) |
---|---|---|---|
접근 속도 | 매우 빠름 | 느림 | 매우 빠름 |
공유 가능성 | O | O | X |
TTL 지원 | O | 일부 가능 | X |
유연성 | 높음 | 높음 | 낮음 |
4. 화이트리스트 방식 vs 블랙리스트 방식
항목 | 화이트리스트 방식 | 블랙리스트 방식 |
---|---|---|
기본 개념 | 유효한 토큰만 저장 | 무효화된 토큰만 저장 |
저장소 부하 | 적음 (최신 토큰만 저장) | 많음 (모든 만료 토큰 저장) |
재사용 공격 대응 | Rotation + 덮어쓰기로 탐지 가능 | 블랙리스트 확인 필요 |
적합한 상황 | Refresh 보안, Rotation 필수 환경 | Access 무효화 필요 시 |
👉 본 시스템은 화이트리스트 + Rotation 방식 사용
5. Refresh Token Redis 관리 구조 설계
저장 구조
- Key:
refresh:user:{email}
- Value: JWT Refresh Token
- TTL: 3일 (72시간)
전체 흐름
- 로그인 → access + refresh 발급
- refresh는 Redis에 저장 (기존 덮어쓰기)
- access 만료 →
/reissue
요청 - Redis에서 refresh 조회 → 일치 확인
- 미일치 시 재사용 공격 간주
- 로그아웃 시 Redis 토큰 삭제
6. 실제 구현 (Spring Boot)
✅ Refresh Token 저장
public void saveRefreshToken(String email, String token) {
redisTemplate.opsForValue().set("refresh:user:" + email, token, 3, TimeUnit.DAYS);
}
✅ Refresh Token 유효성 검증
public void validateRefreshToken(String token) {
if (jwtUtil.isExpired(token)) {
throw new TokenValidationException("Token expired");
}
if (!"refresh".equals(jwtUtil.getCategory(token))) {
throw new TokenValidationException("Invalid token type");
}
String email = jwtUtil.getEmail(token);
String saved = redisTemplate.opsForValue().get("refresh:user:" + email);
if (!token.equals(saved)) {
throw new TokenValidationException("Token not in whitelist");
}
}
✅ 로그아웃 처리
public void removeRefreshToken(String token) {
String email = jwtUtil.getEmail(token);
redisTemplate.delete("refresh:user:" + email);
}
✅ 응답에 토큰 담기
public static void addTokensToResponse(HttpServletResponse response, TokenPair tokens) {
response.setHeader("Authorization", "Bearer " + tokens.getAccessToken());
response.addCookie(CookieUtil.createRefreshTokenCookie(tokens.getRefreshToken()));
}
7. 리팩토링 방향 및 고려사항
1️⃣ 역할 분리
-
TokenService
→ 토큰 생성/검증/저장 -
JWTUtil
→ JWT 생성 및 파싱 -
CookieUtil
,TokenResponseUtil
→ 쿠키 생성 및 응답 구성
2️⃣ 예외 처리 통일
-
TokenValidationException
→ 모든 인증 실패 처리 -
@ControllerAdvice
→ 전역 예외 처리
3️⃣ 설정 외부화
- TTL, Redis Key Prefix 등은
application.yml
로 관리
4️⃣ 테스트 전략
- Redis 없는 환경에선 Mock 또는
embedded-redis
사용
8. 마무리
Redis는 Refresh Token을 안전하게 저장하고, 보안적으로 검증할 수 있는 이상적인 저장소입니다.
화이트리스트 + Rotation 기반 설계를 통해 다음을 달성할 수 있습니다:
- 재사용 공격 방지
- 서버 주도 보안 제어
- Stateless 아키텍처 유지