# JWT Refresh Token Rotation: 보안과 UX, 두 마리 토끼 잡기
Table of Contents
“로그인 풀리면 귀찮고, 안 풀리자니 불안하고”
웹 서비스 인증의 영원한 딜레마입니다. 보안을 위해 Access Token의 유효 기간을 30분으로 짧게 잡으면, 사용자는 30분마다 로그인을 다시 해야 합니다. (최악의 UX죠) 그렇다고 유효 기간을 1달로 늘리면? 해커가 토큰을 탈취했을 때 1달 동안 제 집 드나들듯 계정을 털어갈 수 있습니다.
그래서 나온 대안이 Refresh Token입니다. 짧은 수명의 Access Token(출입증)과 긴 수명의 Refresh Token(재발급권)을 같이 주는 거죠. 하지만 여기에도 맹점이 있습니다. “Refresh Token 자체가 탈취되면 어떡하지?”
이 마지막 구멍까지 막아주는 기술이 바로 **Refresh Token Rotation (RTR)**입니다.
Refresh Token Rotation이란?
개념은 간단합니다. “Refresh Token을 한 번 쓰면 버리고, 새 걸로 바꿔준다.”
기존 방식에서는 Refresh Token 하나로 만료될 때까지 계속 Access Token을 재발급받았습니다. RTR 방식에서는 Access Token을 재발급받을 때마다 Refresh Token도 새로 발급받습니다.
- 클라이언트: (구) Refresh Token 제출 -> “Access Token 주세요”
- 서버: (구) Refresh Token 검증 & 폐기
- 서버: (신) Access Token + (신) Refresh Token 발급
- 클라이언트: (신) 토큰들 저장
이렇게 하면 유효한 Refresh Token이 계속 바뀝니다.
이게 왜 보안에 좋을까? (핵심: 탈취 감지)
RTR의 진가는 토큰 탈취를 감지할 수 있다는 데 있습니다.
해커가 사용자의 Refresh Token(RT_Old)을 훔쳤다고 가정해봅시다.
- 사용자: RT_Old를 사용해 Access Token 갱신 시도 -> 성공! (RT_New 발급받음)
- 해커: 훔친 RT_Old를 사용해 Access Token 갱신 시도
- 서버: “어? RT_Old는 아까 사용자가 이미 써서 폐기된 건데? 또 들어왔네?”
- 서버의 판단: “누군가 토큰을 훔쳐서 재사용(Replay)하려고 한다!”
- 대응: 해당 사용자의 모든 Refresh Token을 즉시 무효화하고 강제 로그아웃시킵니다.
해커는 물론이고, (해커 때문에) 로그인 상태가 풀린 사용자도 다시 로그인을 해야 합니다. 하지만 계정이 털리는 것보다는 훨씬 낫죠. RTR은 “이미 사용된 토큰”이 다시 들어오는 것을 감지하여 공격을 차단합니다.
구현 시 고려사항
1. Race Condition (동시 요청 문제)
프론트엔드에서 여러 API를 동시에 호출하다가 401 Unauthorized가 터지면, 여러 요청이 동시에 토큰 갱신을 시도할 수 있습니다.
첫 번째 요청이 Refresh Token을 바꿔버리면, 두 번째 요청은 “이미 사용된 토큰”을 내밀게 되어 탈취로 오판하고 로그아웃될 수 있습니다.
클라이언트단에서 토큰 갱신 요청은 한 번만 보내도록 큐(Queue)를 구현하거나, 서버에서 짧은 시간(몇 초) 동안은 이전 토큰도 허용해주는 유예 기간(Grace Period)을 두어야 합니다.
2. DB vs Redis
Refresh Token은 상태(사용 여부, 만료 여부)를 관리해야 하므로 저장소가 필요합니다. RTR은 쓰기 작업이 빈번하므로, 빠른 읽기/쓰기가 가능한 Redis가 제격입니다.
마치며
완벽한 보안은 없습니다. 하지만 Refresh Token Rotation은 사용자 경험을 해치지 않으면서 보안 수준을 획기적으로 높일 수 있는 가장 합리적인 타협점입니다.
특히 금융, 개인정보 등 민감한 데이터를 다루는 서비스라면 선택이 아닌 필수입니다. “Access Token 탈취되면 끝 아닌가요?”라는 질문에 이제 자신 있게 대답하세요. “아니요, 저희는 훔친 토큰을 쓰려는 순간 바로 잡아냅니다.”