웹 상태 관리의 역사 — 쿠키에서 세션, 그리고 JWT까지
HTTP는 원래 상태를 기억하지 않는 무상태(stateless) 프로토콜입니다. 그 한계를 넘으려고 쿠키·세션·JWT가 차례로 등장했습니다. 각 기술이 어떤 문제에서 나왔고 무엇인지 연대기로 정리합니다.
쿠키·세션·JWT는 서로 대체재가 아니라, 앞선 방식의 한계를 메우며 이어진 역사의 흐름입니다. 그 인과를 알면 언제 무엇을 쓸지가 보입니다.태초에 HTTP가 있었다
팀 버너스리는 논문을 편하게 공유하려고 월드와이드웹(WWW)을 만들었고, 그 프로토콜이 HTTP입니다. 당시 목적은 단순했습니다 — "문서만 잘 전달하면 된다." 그래서 HTTP는 요청→응답 후 연결을 바로 끊는 구조, 즉 비연결(connectionless)·무상태(stateless) 로 설계됐습니다. 서버 입장에선 요청을 처리하고 바로 잊어버리면 되니 편했습니다.
문제는 웹이 단순 문서 공유를 넘어 애플리케이션으로 발전하면서입니다. 쇼핑몰에서 상품을 장바구니에 담고 결제 페이지로 넘어가면, 서버는 "아까 담던 사람과 같은 사람인가?" 를 전혀 모릅니다. HTTP가 이전 요청과 현재 요청의 관계를 기억하지 못하기 때문입니다. 연결이 끊겨도 사용자를 기억할 방법, 즉 상태 관리가 필요해졌습니다.
첫 시도는 URL에 정보를 붙이는 것이었습니다(...?userId=xxx). 하지만 아이디가 주소창에 노출되고, 그 링크를 공유하면 남이 내 계정으로 로그인되며, 모든 링크에 아이디를 일일이 붙여야 했습니다. 인증 용도로는 부적합했죠.
쿠키 — 상태를 브라우저에 저장하자
1994년 넷스케이프의 루 몬툴리가 장바구니를 구현하려고 쿠키를 고안했습니다. 아이디어는 상태를 서버가 아니라 브라우저에 저장하는 것입니다.
- 서버가 응답에
Set-Cookie헤더로 값을 내려줍니다. - 브라우저가 저장하고, 이후 모든 요청마다
Cookie헤더에 자동으로 실어 보냅니다.
한동안 웹 상태 저장의 사실상 표준이었지만, 두 가지 약점이 있습니다.
- 변조 위험 — 클라이언트에 저장되니 사용자가 개발자 도구·프록시로 값을 바꿀 수 있습니다. 과거 결제 금액을 쿠키에 저장했다가 사용자가 금액을 100원으로 낮춰 결제한 사고가 실제로 있었습니다. XSS(스크립트로 쿠키 탈취)·CSRF(자동 전송을 악용)도 여기서 나옵니다.
- 네트워크 낭비 — 쿠키는 매 요청에 자동 첨부되니, 쿠키가 필요 없는 이미지·CSS 요청에도 늘 따라가 트래픽을 낭비합니다.
세션 — 중요한 건 서버가 기억하자
쿠키의 변조 위험을 풀려고 나온 게 세션입니다. 컨셉은 "클라이언트는 못 믿겠으니, 중요한 건 서버가 기억하겠다" 입니다.
- 서버가 의미 없는 랜덤 문자열 세션 ID를 만듭니다.
- 실제 정보(유저 ID·장바구니 등)는 서버에 저장하고 세션 ID와 매핑합니다.
- 클라이언트에는 세션 ID만 내려주고(보통 쿠키로), 브라우저는 매 요청에 세션 ID만 보냅니다.
중요 정보가 노출되지 않으니 변조가 무력화됩니다. ASP·PHP·JSP 시절 인증의 표준이었습니다. 하지만 한계도 있습니다.
- 서버 확장성 — 서버가 여러 대에 로드밸런서로 분산되면, 1번 서버에서 만든 세션을 다음 요청이 2번 서버로 가면 세션이 없어 사용자를 식별하지 못합니다. 해결책은 스티키 세션(같은 서버로 고정)이나 Redis 같은 중앙 세션 저장소인데, 인프라가 복잡해집니다.
- 메모리 부담 — 사용자가 많아지면 세션 객체도 그만큼 서버 메모리를 차지합니다.
분산 환경에서 세션을 쓰는 구체적 방법과 JWT를 고르는 진짜 이유는 JWT vs 세션(분산 인증)에서 자세히 다뤘습니다.
JWT — 상태를 다시 클라이언트로
세션의 확장성·메모리 문제 때문에, 상태 관리를 다시 클라이언트로 돌려보낸 것이 JWT(JSON Web Token) 입니다.
- 토큰 안에 유저 ID·권한·만료 시간을 담습니다.
- 서버는 토큰의 서명만 검증하고 별도로 상태를 기억하지 않습니다 → DB 조회 없이 즉시 인증.
- 요청 시
Authorization: Bearer <토큰>형태로 보냅니다.
구조는 헤더 . 페이로드 . 서명 세 부분이고, 헤더·페이로드는 암호화가 아니라 Base64 인코딩이라 누구나 디코딩해 내용을 볼 수 있습니다. 즉 JWT는 암호화 토큰이 아니라 서명 토큰입니다 — 내용은 공개되고 위·변조만 막습니다.
서버가 상태를 안 가지니 스케일 아웃이 쉽고, 모바일·MSA 환경과 궁합이 좋습니다. 대신 약점이 있습니다.
- 크기 — 정보를 많이 담을수록 매 요청 전송량이 커집니다.
- 강제 만료 불가(가장 큰 문제) — 한 번 발급되면 만료 전까지 서버가 폐기할 수 없습니다. 탈취되면 만료까지 공격자가 계속 씁니다.
완화책은 리프레시 토큰입니다. 액세스 토큰의 수명을 짧게 두고, 긴 수명의 리프레시 토큰으로 갱신합니다. 다만 무상태를 지향하는 JWT에서도 리프레시 토큰은 서버에 저장해야 하고, 이게 탈취되면 피해가 더 큽니다.
현금과 같은 토큰 — Bearer의 의미
JWT의 성격을 가장 잘 보여주는 비유가 현금입니다. Bearer는 소유자가 아니라 가지고 있는 사람(소지자) 을 뜻합니다. 현금은 위조는 어렵지만 가진 사람이 주인이라, 도둑이 훔친 현금도 그대로 쓸 수 있습니다. JWT도 똑같습니다 — 토큰만 유효하면 누가 써도 막지 않습니다. 이건 결함이 아니라 설계 사상입니다. 그래서 토큰은 HTTPS로만 주고받고, 로컬 스토리지 대신 HttpOnly 쿠키에 담고(XSS 완화), SameSite로 CSRF를 막고, 서버 로그에 Authorization 헤더가 남지 않게 하는 등 "훔치지 못하게" 하는 게 우선입니다.
세션 vs JWT — 무엇을 쓸까
요즘 인증은 JWT가 대세지만, 만병통치약은 없습니다.
- 보안이 더 중요할 때 — 세션이 유리할 수 있습니다. 서버가 상태를 쥐고 있어 즉시 무효화가 됩니다.
- 확장성·모바일 — JWT가 압도적으로 유리합니다.
정리
- HTTP는 무상태 — 그 한계를 넘으려 상태 관리가 필요해졌다(URL 방식은 노출·공유 사고로 부적합).
- 쿠키 — 상태를 브라우저에 저장(
Set-Cookie/Cookie). 변조 위험 + 정적 리소스에도 자동 첨부. - 세션 — 중요 정보는 서버에, 클라이언트엔 세션 ID만. 변조는 막지만 서버 확장성·메모리 부담.
- JWT — 상태를 다시 클라이언트로. 서명 토큰(≠암호화), 무상태라 확장 쉬움. 대신 강제 만료 불가 → 짧은 액세스 + 리프레시 토큰.
- Bearer = 소지자가 주인 — 훔치지 못하게(HTTPS·HttpOnly·SameSite) 지키는 게 우선.
각 방식은 앞선 방식의 한계에서 나왔습니다. 흐름을 알면 "왜 이 방식인가"가 보이고, 그때 비로소 상황에 맞는 선택을 할 수 있습니다.
