
클라우드 네이티브와 마이크로서비스 아키텍처(MSA) 시대에 'Stateless(무상태)'하다는 것은 꽤 중요한 특징이 되었다. 그리고 인증 과정에서 무상태를 위한 방법으로 JWT(JSON Web Token)가 자리 잡고 있다. 서버에 상태를 저장하지 않으니 확장성이 뛰어나고 토큰 자체에 모든 정보가 있으니 편리하다는 말은 설득력이 강했다. 그리고 지금까지 여러 프로젝트를 진행해오면서 본인부터 주변 동료들까지 대부분 JWT를 당연하게 사용하기에 뭐가 문제가 되는지 생각조차 안해본 것 같다
이번에 패션 플랫폼 ‘바로’를 개발하며 대규모 시스템의 인증 방식을 고민했고, 자연스럽게 JWT를 유력한 후보로 검토했다. 하지만 깊이 파고들수록 JWT가 과연 만능 해결책인지에 대한 근본적인 의문이 생기기 시작했다. 따라서 전통적인 세션 방식부터 최신 토큰 방식까지 검토하며 겪었던 고민과, 결국 왜 세션 기반의 '세션 저장소' 방식을 선택하게 되었는지에 대한 기록을 남겨보려고 한다
🧐 세션 관리, 어디서부터 잘못됐을까?
먼저 기존 세션 관리 방식들이 클라우드 환경에서 왜 기피되는지 간단히 짚어보려 한다
1. 스티키 세션 (Sticky Session)
- 개념 : 로드 밸런서가 특정 사용자의 요청을 항상 동일한 서버(WAS)로 보내는 방식이다. 세션 정보가 해당 서버에만 저장되므로 구현이 간단하다
- 문제점 : 특정 서버에 트래픽이 몰릴 수 있고, 해당 서버에 장애가 발생하면 세션 정보가 모두 유실된다. 무엇보다 서버를 자유롭게 늘리거나 줄이는(Scale Out/In) 클라우드 환경의 핵심 장점을 활용할 수 없다
2. 세션 클러스터링 (Session Clustering)
- 개념 : 모든 서버가 모든 사용자의 세션 정보를 복제하여 저장한다. 어떤 서버로 요청이 가도 처리할 수 있다
- 문제점 : 서버 수가 늘어날수록 세션을 복제하는 과정에서 발생하는 네트워크 부하와 동기화 오버헤드가 기하급수적으로 커진다. 잦은 배포와 스케일링이 일어나는 환경에서는 부담이 너무 크다
→ 이러한 문제들 때문에 많은 시스템이 서버를 무상태(Stateless)로 만들기 위해 노력하게 된다
✨ 토큰과 세션 저장소
서버를 무상태로 만들기 위한 현대적인 접근법은 크게 두 가지로 나뉜다. 바로 전통적인 방식의 진화형인 세션 저장소와 밸류 토큰(JWT)이다.
3. 세션 저장소 방식
- 개념 : 사용자의 로그인 상태 정보(세션)를 WAS가 아닌 외부의 독립된 저장소(주로 Redis와 같은 인메모리 DB)에 저장하는 방식이다
- 동작
- 사용자 로그인 시, 서버는 세션 정보를 생성해 세션 저장소에 저장하고, 고유한
Session ID를 클라이언트에게 쿠키로 발급한다 - 이후 클라이언트는 매 요청마다
Session ID를 서버에 전달한다 - 서버는
Session ID를 받아 세션 저장소에서 실제 세션 정보를 조회하여 사용자를 인증/인가한다
- 사용자 로그인 시, 서버는 세션 정보를 생성해 세션 저장소에 저장하고, 고유한
- 장점 : 모든 서버가 외부 저장소를 바라보므로 서버(WAS) 자체는 완벽한 무상태가 된다. 자유로운 스케일링이 가능하다
- 단점 : 세션 저장소가 SPOF에 놓이게 된다. 저장소에 장애가 발생하면 시스템 전체의 인증 기능이 마비된다
4. 밸류 토큰 방식 (대표적으로 JWT)
- 개념 : 사용자 정보 및 권한 등 필요한 데이터를 토큰 자체에 담아 암호화한 뒤 클라이언트에게 전달하는 방식이다. 서버는 토큰을 받으면 저장소 조회 없이 스스로 토큰을 검증하고 사용자 정보를 얻을 수 있다
- 장점
- 완벽한 무상태 : 서버는 상태를 저장하지도, 외부 저장소를 조회하지도 않는다
- SPOF 없음 : 인증을 위해 의존하는 중앙 저장소가 없으므로 특정 컴포넌트의 장애가 전체 시스템의 장애로 이어지지 않는다. 대규모 분산 시스템에 매우 이상적인 구조다
- 단점 : 바로 이 지점에서 고민이 시작되었다. 아래에서 자세히 설명하겠다
💔 JWT의 한계, ‘발급된 토큰은 통제 불가’
밸류 토큰 방식의 가장 큰 장점인 '서버의 무관여'는 역설적으로 가장 큰 단점을 만들어냈다. 한번 발급된 토큰은 만료 시간이 되기 전까지는 사실상 무적이다.
⚠️ 밸류 토큰 방식이 기본적으로 지원하지 못하는 기능들
- 강제 로그아웃 : 관리자가 특정 사용자를 시스템에서 즉시 추방해야 할 때, 이미 발급된 토큰을 무효화할 방법이 없다. 사용자는 토큰 만료 시점까지 계속 시스템을 이용할 수 있다
- 자동 로그아웃 (세션 타임아웃) : "30분 동안 활동이 없으면 자동 로그아웃"과 같은 기능을 구현할 수 없다. 토큰은 발급 시점에 정해진 만료 시간만 알 뿐, 사용자의 마지막 활동 시간을 추적하지 못한다
- 조기 폐기된 토큰 식별 : 사용자가 스스로 '로그아웃' 버튼을 눌러도, 서버는 해당 토큰이 더 이상 사용되면 안 된다는 사실을 알지 못한다. 만약 이 토큰이 탈취된다면 만료 전까지 재사용될 수 있다
이러한 기능들은 대부분의 서비스에서 보안 및 사용자 경험상 필수적인 요구사항이다. 그렇다면 이 문제들을 해결하기 위해 어떤 방법들이 있을까?
🛠️ JWT의 한계를 개선하기 위한 방법
결국 JWT의 한계를 보완하려면, "폐기된 토큰 목록" 이라는 새로운 '상태' 를 어딘가에 저장해야 했다. 이를 개선하기 위한 방법으로 Sync 방식이나 Async 방식은 각각의 방식으로 로그아웃 및 자동 로그아웃 기능을 구현할 수 있게 한다
1. Sync (동기) 방식 - 실시간으로 폐기 목록 조회
구현
- 사용자가 로그아웃하면, 해당 토큰의 고유 식별자(예:
jti클레임)를 Redis와 같은 토큰 저장소에 저장한다. (이것을 '블랙리스트'라고 부른다) - 모든 API 요청이 들어올 때마다, 서버는 JWT의 유효성(서명, 만료 시간)을 검증한 후, 동기적으로 토큰 저장소에 접속해 해당 토큰이 블랙리스트에 있는지 확인한다
문제점
- JWT의 장점 상실 : SPOF가 없다는 JWT의 가장 큰 장점이 사라졌다. 토큰 저장소(Redis)에 장애가 발생하면 어떻게 될까? 만약 장애 시 모든 요청을 실패 처리한다면, 이는 '세션 저장소' 방식과 동일한 SPOF를 갖게 된다
- 성능 저하 : 모든 요청마다 네트워크를 통해 외부 저장소를 한번 더 조회해야 하므로 응답 시간이 늘어난다
- 어정쩡한 결과 : 결국 '세션 저장소' 방식과 거의 동일한 구조가 되었는데, 과정은 ‘JWT 복호화 + 토큰 저장소 조회’ 로 오히려 더 복잡해졌다
2. Async (비동기) 방식: 로컬에서 폐기 목록 관리
구현
- 사용자가 로그아웃하면, IDP(인증서버)는 해당 토큰이 폐기되었다는 이벤트를 메시지 큐(Kafka 등)로 발행한다
- 각각의 API 서버들은 이벤트를 구독하여, 전달받은 폐기 토큰 정보를 자신의 로컬 메모리(캐시)에 저장한다
- API 요청 시, 서버는 외부 저장소가 아닌 자신의 로컬 캐시를 조회하여 토큰의 폐기 여부를 확인한다.
문제점
- 구현 복잡성 : 메시지 큐라는 새로운 인프라가 필요하며, 이벤트 기반 아키텍처는 동기 방식보다 훨씬 복잡하다
- 데이터 불일치 : 비동기 처리의 특성상, 토큰이 폐기된 시점과 각 서버가 그 사실을 인지하는 시점 사이에 미세한 딜레이(Eventual Consistency)가 존재한다. 이 짧은 시간 동안 폐기된 토큰이 사용될 수 있는 가능성이 있다. 다만 이는 자동 로그아웃과 같은 기능의 정밀도가 낮아지는 수준이라 큰 이슈가 될 것 같지는 않다
🔄 돌고 돌아, 다시 '세션 저장소'로!
JWT의 한계를 보완하려는 방법들을 검토한 결과, 이러한 결론에 도달했다.
필수 기능을 구현하기 위해 JWT에 보완책을 더하는 순간, 그 구조는 결국 '세션 저장소' 방식과 비슷해지거나
혹은 더 복잡하고 비싼 대가를 치러야 한다.
특히 패션 플랫폼 ‘바로’의 경우 도메인 특성을 고려했을 때, JWT의 한계는 단순한 불편함을 넘어 치명적인 비즈니스 리스크가 될 수 있었다
- 🛒 장바구니 상태 관리 : 고객의 장바구니는 매우 중요한 '상태' 정보다. 비로그인 상태에서 담은 상품이 로그인 후에도 유지되어야 하고, 여러 기기에서 동일한 장바구니를 봐야 한다. 이는 서버에서 일관되게 관리되어야 할 정보이며, 중앙 세션 저장소는 이를 매우 자연스럽게 처리한다
- 🔒 강력한 보안 요구사항 : 고객 계정이 탈취되었을 경우, 부정 주문을 막기 위해 즉시 해당 세션을 종료시켜야 한다. 통제가 불가능한 JWT로는 이러한 즉각적인 대응이 어렵다. 또한 결제 과정처럼 민감한 정보를 다루는 단계에서 서버가 사용자의 세션을 명확히 통제할 수 있다는 점은 매우 중요하다
- 💳 복잡한 주문 프로세스 : 상품 선택, 쿠폰 적용, 배송지 입력, 결제 등 여러 단계로 이루어진 주문 과정의 상태를 관리하는 데 서버사이드 세션은 매우 안정적이고 효과적인 해결책이다
결국 '세션 저장소' 방식을 최종적으로 선택한 이유는 아래와 같다
- 단순함과 기능 완성도
'세션 저장소' 방식은 우리가 필요로 하는 모든 기능(강제 로그아웃, 장바구니 등)을 구현하는데 참고할 수 있는 자료들이 많아 개발 리소스를 줄일 수 있다. 또한 이러한 구조는 직관적이고 수십 년간 검증된 안정적인 패턴이다 - 보안
JWT의 경우 라이브러리의 취약점으로 인한 보안 문제 발생 가능성이 있어 주기적으로 최신 버전 사용 및 점검이 필요하다. 반면 세션의 경우 이러한 부분에서 리소스가 들지 않으며, 세션 ID는HttpOnly, Secure옵션을 가진 쿠키를 통해 안전하게 관리할 수 있어, 자바스크립트를 통한 탈취(XSS) 공격에 비교적 안전하다 - SPOF는 피할 수 없다
JWT의 한계를 보완한 'Sync 방식'은 결국 '토큰 저장소'라는 새로운 SPOF를 만든다. 어차피 중앙화된 저장소의 장애 가능성을 감수해야 한다면, 기능이 완벽하고 구조가 단순하며 보안적으로 더 성숙한 '세션 저장소' 방식을 선택하는 것이 훨씬 합리적이었다. 세션 저장소(Redis 등)는 클러스터링을 통해 고가용성(HA)을 확보하는 기술과 노하우는 이미 널리 퍼져 있다.
정리하자면,
- 로그아웃 기능이 전혀 필요 없거나, 토큰 만료 시간이 매우 짧아 탈취 위험이 적은 일부 시스템에서는
- JWT 방식이 여전히 선택지가 될 수 있다
- 하지만 다양한 상태 정보와 보안 요구사항, 안정적인 기능 구현이 중요한 패션 플랫폼에서는,
- 최신 트렌드를 무작정 따르기보다 문제의 본질을 파고들어 가장 '우리 서비스에 적합한' 기술을 선택하는 것이 중요하다고 판단했다.
- 결과적으로 JWT는 훌륭한 기술이지만, 모든 상황에 맞는 만능 열쇠는 아니라고 생각한다
'프로젝트' 카테고리의 다른 글
| [바로] DeadLock 범인 찾기 (Ft. 위험한 FK?) (3) | 2025.08.25 |
|---|---|
| [바로] 단일 주문 성능 개선 삽질기 (Ft. JPA save, FK) (0) | 2025.08.20 |
| [바로] 반복되는 인증,인가 처리 없애버리기(Ft. AOP & ArgumentResolver) (0) | 2025.08.08 |
| [바로] 분산 시스템에서 ID가 유일하려면?(Ft. Snowflake VS TSID 성능테스트) (3) | 2025.07.28 |
| [바로] 스와이프로 찾는 내 스타일, ‘바로’를 기획하며..(Ft. 기술적 목표) (3) | 2025.07.22 |