기존 방식: JWT 단일 토큰
초기에는 Access Token 하나만 사용하는 구조였다.
로그인
→ Access Token 발급 (유효기간 7일)
→ 만료 시 재로그인
운영 과정에서 다음과 같은 딜레마가 발생했다.
문제점
1. 토큰 유효기간을 길게 설정할 경우
- 토큰 탈취 시 7일 동안 제3자가 자유롭게 사용 가능
- JWT는 Stateless 특성으로 인해 서버에서 토큰을 강제로 무효화할 수 없음
- 로그아웃을 하더라도 토큰은 만료 시점까지 계속 유효
결과적으로 보안 사고 발생 시 대응이 불가능하다.
2. 토큰 유효기간을 짧게 설정할 경우
- 30분마다 재로그인이 필요
- 사용자 경험(UX)이 크게 저하됨
- 실제 서비스 환경에서는 사용자가 이탈할 가능성이 높아짐
해결책: Access Token + Refresh Token 구조 도입
보안과 사용자 경험을 동시에 만족시키기 위해
Access Token과 Refresh Token을 분리하여 사용한다.
| 토큰 종류 | 역할 | 만료 시간 | 관리 주체 |
|---|---|---|---|
| Access Token | API 요청 인증 | 30분 | 클라이언트 |
| Refresh Token | Access Token 재발급 | 7일 | 서버(Redis) |
왜 더 안전한가?
| 상황 | Access Token만 사용 | Access + Refresh 사용 |
|---|---|---|
| Access Token 탈취 | 7일간 피해 발생 | 최대 30분만 피해 |
| Refresh Token 탈취 | 해당 없음 | Redis에서 즉시 무효화 가능 |
| 로그아웃 | 토큰 만료까지 유효 | Redis 삭제로 즉시 무효화 |
| 서버 제어 | 불가능 | 가능 |
핵심 구현 포인트
1. Refresh Token을 Redis에 저장
// 로그인 시
redisTemplate.set("refresh:1", refreshToken, 7일);
// 로그아웃 시
redisTemplate.delete("refresh:1"); // 즉시 무효화
- Refresh Token을 서버에서 관리하여 강제 로그아웃 및 만료 제어 가능
- JWT의 Stateless 특성을 보완하는 핵심 요소
2. Refresh Token Rotation 적용
// 토큰 갱신 시마다 새 Refresh Token 발급
String newRefreshToken = generateRefreshToken(memberId);
redisTemplate.set("refresh:1", newRefreshToken); // 기존 토큰 덮어쓰기
- Refresh Token 재사용 공격 방지
- 탈취된 Refresh Token으로 재발급 시 Redis 불일치로 차단
3. 프론트엔드 자동 토큰 갱신 (Axios Interceptor)
// 401 에러 발생 시 자동으로 토큰 재발급 후 재요청
if (status === 401) {
const newTokens = await reissue(refreshToken);
return api(originalRequest);
}
- 사용자는 로그인 만료를 인지하지 않음
- 서비스 사용 흐름이 끊기지 않음
결론
| 항목 | 기존 방식 | 개선 후 |
|---|---|---|
| 보안 | 토큰 탈취 시 대응 불가 | 피해 시간 최소화 + 즉시 무효화 |
| 사용자 경험 | 주기적 재로그인 필요 | 자동 갱신으로 끊김 없음 |
| 서버 제어 | 불가능 | 로그아웃 및 강제 만료 가능 |
JWT의 Stateless 장점은 유지하면서,
Refresh Token과 Redis를 통해 서버의 제어권을 확보한 구조다.
현재는 AT에 만료기간을 RT와 같이 일주일로 수정하였다.
이유는 redis 클라우드를 통해 팀원들과 RT저장을 공유하며 사용하다보니 두 팀원이 같은 아이디로 로그인을하였을 때 다른 한 팀원에 AT가 만료되어 RT가 AT를 재발급을 하게 되면 다른 팀원이 사용했던 기존 AT와 RT에 의해 바뀐 새 AT와 달라지기 때문에 로그아웃이 되는 문제가 생겨 개발단계에서는 AT기간을 늘려서 개발하기로 결정하고 개발 완료시 다시 AT의 만료시간을 30분으로 줄일 예정이다.
'-- 오늘 있었던 개발 일기' 카테고리의 다른 글
| Redis 특징 완벽 정리! (0) | 2026.02.20 |
|---|---|
| Refresh Token을 HttpOnly Cookie로 변경한 이유! (0) | 2026.02.12 |
| 트랜잭션의 격리 수준 (0) | 2026.02.01 |
| 더미 데이터에 시퀀스? (1) | 2026.01.29 |
| 테이블 설계... (0) | 2026.01.28 |