2025-09-22 23:54
-
쿠키와 세션은 HTTP의 비상태성(Stateless)을 보완하여 서버가 클라이언트를 식별하고 상태 정보를 유지하기 위해 사용되는 핵심 기술이다.
-
쿠키는 클라이언트 측에 작은 텍스트 파일로 정보를 저장하여 재방문 시 서버가 사용자를 인식하게 하며, 세션은 서버 측에 사용자 정보를 저장하고 고유한 세션 ID를 통해 클라이언트를 구별한다.
-
현대 웹 애플리케이션의 로그인, 장바구니, 개인화 설정 등 필수적인 사용자 경험은 쿠키와 세션의 상호작용을 통해 구현되며, 보안을 위해 다양한 속성과 관리 전략이 요구된다.
쿠키와 세션 완벽 정복 핸드북 STATELESS 세상의 등대
들어가며 웹은 왜 당신을 기억하지 못할까
인터넷의 바다를 항해하는 당신의 웹 브라우저는 수많은 웹사이트를 방문한다. 하지만 이상하게도 이 똑똑해 보이는 웹은 당신이 누구인지, 방금 무슨 일을 했는지 기억하지 못한다. 마치 단기 기억상실증에 걸린 물고기처럼 말이다. 그 이유는 웹 통신의 기반이 되는 HTTP(HyperText Transfer Protocol)가 **비상태성(Stateless)**이라는 근본적인 특징을 가지고 있기 때문이다.
HTTP는 클라이언트(당신의 브라우저)가 서버에 요청을 보내면, 서버는 그에 맞는 응답을 보내고 연결을 끊어버리는 단순한 구조로 설계되었다. 각 요청은 이전 요청과 아무런 관련이 없는 독립적인 사건으로 취급된다. 이는 서버의 부담을 줄이고 불특정 다수를 대상으로 빠르고 효율적인 통신을 가능하게 했지만, 심각한 문제를 낳았다.
온라인 쇼핑몰에서 물건을 장바구니에 담았다고 생각해보자. 다음 페이지로 이동하는 순간, 서버는 당신이 방금 장바구니에 무언가를 담았다는 사실을 잊어버린다. 로그인을 해도, 다음 페이지에서는 다시 당신을 새로운 방문자로 취급할 것이다. 이처럼 상태를 유지할 수 없다면 현대적인 웹 서비스는 거의 불가능하다.
이러한 HTTP의 한계를 극복하고, 서버가 클라이언트를 기억하게 만들기 위해 탄생한 기술이 바로 **쿠키(Cookie)**와 **세션(Session)**이다. 이 둘은 웹이라는 거대한 바다에서 당신의 배(브라우저)를 식별해주는 등대와 같은 역할을 한다. 이번 핸드북에서는 쿠키와 세션이 왜 만들어졌는지, 어떤 구조로 작동하며, 어떻게 사용되는지, 그리고 더 나아가 깊은 보안 문제까지 완벽하게 파헤쳐 볼 것이다.
제1장 쿠키의 탄생 상태 저장의 첫걸음
1.1 만들어진 이유 넷스케이프의 고민
1994년, 웹의 여명기. 넷스케이프 커뮤니케이션즈(Netscape Communications)의 개발자였던 루 몬툴리(Lou Montulli)는 중대한 과제에 직면했다. 그는 가상 상점을 구축하고 있었는데, 사용자들이 페이지를 이동할 때마다 장바구니 내용이 사라지는 문제, 즉 HTTP의 비상태성 문제와 씨름하고 있었다. 서버는 어떤 요청이 어떤 사용자의 장바구니에 해당하는지 전혀 알 수 없었다.
이 문제를 해결하기 위해 그는 클라이언트 측에 작은 데이터를 저장하는 아이디어를 떠올렸다. 서버가 클라이언트에게 작은 메모 조각(데이터)을 보내면, 클라이언트는 그것을 보관하고 있다가 다음 요청 시 다시 서버에게 전달하는 방식이다. 이 메모 조각을 통해 서버는 “아, 이 요청은 아까 그 사용자의 요청이구나!”라고 식별할 수 있게 된다. 이것이 바로 쿠키의 탄생 배경이다.
‘쿠키’라는 이름은 유닉스 프로그래밍에서 유래한 ‘매직 쿠키(Magic Cookie)‘라는 용어에서 따왔다. 이는 프로그램 간에 전달되는 데이터 패킷을 의미하는데, 받은 쪽에서 내용을 수정하지 않고 그대로 돌려주는 특징이 있다. 웹 쿠키도 서버가 준 정보를 브라우저가 그대로 돌려준다는 점에서 이 개념과 유사하다.
1.2 쿠키의 구조와 작동 원리
쿠키는 서버가 생성하여 HTTP 응답 헤더(Response Header)에 담아 클라이언트에게 전달하는 작은 텍스트 데이터 조각이다. 클라이언트(웹 브라우저)는 이 쿠키를 받아 자신의 저장소에 보관한다.
작동 순서:
-
최초 요청: 클라이언트가 서버에 처음으로 요청을 보낸다.
-
쿠키 생성 및 전송: 서버는 클라이언트를 식별하거나 기억할 필요가 있는 정보를 담아 쿠키를 생성한다. 그리고 이 쿠키를
Set-Cookie라는 응답 헤더에 담아 클라이언트에게 보낸다. -
쿠키 저장: 클라이언트는 서버로부터 받은 쿠키를 내부 저장소(메모리 또는 하드디스크)에 저장한다.
-
쿠키와 함께 요청 전송: 이후 클라이언트는 **동일한 서버(도메인)**에 요청을 보낼 때마다, 저장하고 있던 쿠키를
Cookie라는 요청 헤더에 담아 함께 전송한다. -
서버의 식별: 서버는 요청 헤더에 담겨 온 쿠키를 읽고, 이전에 자신이 보냈던 클라이언트임을 식별하고 상태 정보를 활용하여 적절한 응답을 보낸다.
이 과정을 통해 서버는 매 요청이 독립적이라는 HTTP의 한계를 넘어, 연속적인 상호작용을 하는 것처럼 클라이언트를 대할 수 있게 된다.
1.3 쿠키의 구성 요소 자세히 들여다보기
쿠키는 단순히 Key=Value 쌍으로만 이루어져 있지 않다. 쿠키의 행동을 제어하는 여러 속성(Attribute)들이 함께 정의된다.
| 속성 (Attribute) | 설명 | 예시 |
|---|---|---|
Name=Value | 쿠키의 이름과 값. 실제 저장되는 데이터. | userId=gemini |
Expires | 쿠키의 만료 날짜. 이 날짜가 지나면 쿠키는 자동으로 삭제된다. (오래된 방식) | Expires=Tue, 21-Sep-2027 11:51:22 GMT |
Max-Age | 쿠키의 유효 기간(초 단위). Expires보다 우선순위가 높고 현대적인 방식. 0이나 음수 값은 쿠키를 즉시 삭제. | Max-Age=3600 (1시간) |
Domain | 쿠키가 전송될 서버의 도메인을 지정. 지정하지 않으면 쿠키를 설정한 서버의 도메인으로 자동 설정. | Domain=.google.com |
Path | 쿠키가 전송될 서버의 특정 경로를 지정. 이 경로와 그 하위 경로에서만 쿠키가 활성화된다. | Path=/ (모든 경로) |
Secure | HTTPS 프로토콜을 사용하는 보안 연결에서만 쿠키가 전송되도록 설정. | Secure |
HttpOnly | 자바스크립트의 document.cookie API로 쿠키에 접근하는 것을 막는다. XSS(Cross-Site Scripting) 공격 방어에 필수적. | HttpOnly |
SameSite | 다른 도메인에서 요청이 발생할 때 쿠키 전송을 제어하여 CSRF(Cross-Site Request Forgery) 공격을 방어. | SameSite=Strict, SameSite=Lax, SameSite=None |
예시:
HTTP
Set-Cookie: userId=gemini; Max-Age=3600; Domain=.example.com; Path=/; Secure; HttpOnly; SameSite=Lax
이 쿠키는 userId라는 이름에 gemini라는 값을 가지며, 1시간 동안 유효하다. .example.com과 그 하위 도메인으로 HTTPS를 통해 요청할 때만 전송되며, 자바스크립트로는 접근할 수 없고, 대부분의 크로스 사이트 요청에서는 전송되지 않는다.
제2장 세션의 등장 쿠키의 한계를 넘어서
쿠키는 획기적인 발명이었지만 명확한 한계를 가지고 있었다. 모든 정보가 클라이언트 측에 저장되다 보니 보안에 취약했고, 저장할 수 있는 데이터의 양도 4KB로 제한적이었다. 사용자의 ID나 비밀번호 같은 민감한 정보를 쿠키에 그대로 담아 보내는 것은 매우 위험한 일이었다.
2.1 만들어진 이유 더 안전하고 유연한 상태 관리
이러한 쿠키의 단점을 보완하기 위해 세션이 등장했다. 세션의 핵심 아이디어는 **“중요한 정보는 서버에 저장하고, 클라이언트에게는 그 정보를 식별할 수 있는 열쇠(ID)만 주자”**는 것이다.
서버는 각 클라이언트마다 고유한 저장 공간(세션 저장소)을 마련하고, 그곳에 사용자 관련 데이터를 저장한다. 그리고 이 저장 공간에 접근할 수 있는 무작위하고 추측 불가능한 긴 문자열, 즉 **세션 ID(Session ID)**를 생성하여 쿠키에 담아 클라이언트에게 보낸다.
클라이언트는 이 세션 ID 쿠키만 가지고 있다가 요청 시마다 서버로 전송한다. 서버는 클라이언트가 보낸 세션 ID를 보고, 서버의 세션 저장소에서 해당 ID와 매칭되는 데이터를 찾아 사용자의 상태를 파악한다. 마치 우리가 은행에서 계좌번호(세션 ID)를 알려주면 은행원(서버)이 내부 시스템(세션 저장소)에서 우리 정보를 찾아 업무를 처리해주는 것과 같다.
2.2 세션의 구조와 작동 원리
세션은 쿠키를 기반으로 동작하지만, 데이터 저장의 주체가 서버라는 점에서 근본적인 차이가 있다.
작동 순서:
-
최초 요청: 클라이언트가 서버에 처음 요청을 보낸다. (세션 ID 쿠키가 없음)
-
세션 생성 및 ID 발급: 서버는 클라이언트를 위한 고유한 세션 객체를 서버 메모리나 데이터베이스에 생성한다. 그리고 이 세션을 식별할 수 있는 매우 긴 무작위 문자열인 세션 ID를 생성한다.
-
세션 ID 쿠키 전송: 서버는 생성한 세션 ID를
Set-Cookie헤더에 담아 클라이언트에게 응답으로 보낸다. 보통 이 쿠키의 이름은JSESSIONID(Java),PHPSESSID(PHP) 등 플랫폼마다 관례적인 이름을 가진다. -
세션 ID 저장: 클라이언트는 이 세션 ID가 담긴 쿠키를 브라우저에 저장한다.
-
세션 ID와 함께 요청: 이후 클라이언트는 서버에 요청할 때마다 이 세션 ID 쿠키를
Cookie헤더에 담아 함께 전송한다. -
서버의 세션 데이터 조회: 서버는 요청에 담겨 온 세션 ID를 확인하고, 서버의 세션 저장소에서 해당 ID에 매칭되는 세션 데이터를 찾아 사용자의 상태(예: 로그인 여부, 장바구니 내용)를 파악하고 요청을 처리한다.
2.3 세션의 생명주기
세션은 영원히 유지되지 않는다. 세션은 생성되고, 사용되며, 결국 소멸하는 생명주기를 가진다.
-
생성: 클라이언트가 처음 접속하여 서버가 세션 ID를 발급하는 시점.
-
유지: 클라이언트가 세션 ID를 통해 계속해서 서버와 상호작용하는 동안 세션은 유효하게 유지된다. 서버는 클라이언트의 마지막 요청 시간을 기준으로 **타임아웃(Timeout)**을 설정한다. 만약 설정된 시간(예: 30분) 동안 클라이언트로부터 아무런 요청이 없으면, 서버는 해당 세션이 만료되었다고 판단한다.
-
소멸(무효화):
-
타임아웃: 설정된 시간 동안 활동이 없어 세션이 만료될 때.
-
명시적 무효화: 사용자가 ‘로그아웃’ 버튼을 클릭하는 경우처럼, 서버에서
session.invalidate()와 같은 코드를 호출하여 세션을 강제로 삭제할 때. -
서버 종료: 웹 서버가 재시작되면 메모리에 저장된 세션 정보는 대부분 사라진다. (이를 방지하기 위해 세션을 데이터베이스나 별도의 세션 스토어에 저장하기도 한다.)
-
제3장 쿠키 vs 세션 무엇을 언제 써야 할까
쿠키와 세션은 상호 보완적인 관계이며, 각각의 장단점이 명확하여 사용 목적에 따라 선택해야 한다.
| 구분 | 쿠키 (Cookie) | 세션 (Session) |
|---|---|---|
| 저장 위치 | 클라이언트 (브라우저) | 서버 (메모리, DB, 캐시 서버 등) |
| 저장 데이터 | 텍스트 (Key-Value) | 객체를 포함한 모든 타입의 데이터 |
| 생명 주기 | 브라우저 종료 시 소멸 (세션 쿠키) 또는 Max-Age까지 유지 (영속 쿠키) | 서버에서 설정한 타임아웃까지 유지 또는 명시적 소멸 시까지 |
| 보안 | 클라이언트에 저장되어 변조 및 탈취에 취약 | 서버에 저장되어 상대적으로 안전 (세션 ID 탈취 위험은 존재) |
| 속도 | 빠름 | 쿠키보다 약간 느림 (서버에서 조회 과정 필요) |
| 서버 부하 | 거의 없음 | 사용자가 많아지면 서버 메모리 부하 증가 |
| 주요 용도 | - 아이디 저장, 자동 로그인 - 오늘 하루 보지 않기 팝업 - 사용자 맞춤 광고 (트래킹 쿠키) | - 로그인 상태 유지 - 장바구니 정보 관리 - 권한 정보 등 민감한 데이터 관리 |
비유로 이해하기:
-
쿠키: 놀이공원의 자유이용권 팔찌와 같다. 팔찌(쿠키)만 보여주면 직원이 “아, 이 사람은 이용권을 구매했구나”라고 인식하고 놀이기구에 태워준다. 팔찌에 모든 정보(이름, 나이 등)를 적어두면 분실 시 위험하다.
-
세션: 찜질방의 락커 키와 같다. 당신은 락커 키(세션 ID 쿠키)만 가지고 있고, 당신의 옷과 소지품(중요 정보)은 찜질방의 락커(서버 세션 저장소)에 안전하게 보관된다. 키를 잃어버리지 않는 이상 소지품은 안전하다.
제4장 심화 내용 보안과 확장성
쿠키와 세션은 편리하지만, 제대로 관리하지 않으면 심각한 보안 위협에 노출될 수 있다.
4.1 세션 하이재킹과 CSRF 공격
-
세션 하이재킹 (Session Hijacking): 공격자가 다른 사용자의 세션 ID를 탈취하여 그 사용자인 것처럼 위장하는 공격이다. 주로 네트워크 스니핑(무선랜 도청 등)이나 XSS 공격을 통해 세션 ID 쿠키를 훔쳐낸다.
-
대응 방안:
-
HttpOnly속성을 사용하여 자바스크립트가 쿠키에 접근하지 못하게 막는다. -
Secure속성을 사용하여 HTTPS 통신에서만 쿠키가 전송되도록 한다. -
세션 타임아웃을 짧게 설정하고, 로그인 등 중요한 작업 시 세션 ID를 재발급한다.
-
-
-
CSRF (Cross-Site Request Forgery): 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(글 삭제, 비밀번호 변경 등)를 특정 웹사이트에 요청하게 만드는 공격이다. 사용자가 이미 로그인된 상태(세션 쿠키가 있는 상태)에서 공격자가 만들어 놓은 악성 링크를 클릭하면, 브라우저는 자동으로 세션 쿠키를 포함하여 요청을 보내기 때문에 발생한다.
-
대응 방안:
-
SameSite=Strict또는SameSite=Lax속성을 설정하여 크로스 사이트 요청 시 쿠키 전송을 제한한다. -
Referer 헤더를 확인하여 요청의 출처가 정상적인지 검증한다.
-
CSRF 토큰(무작위 값)을 사용하여 매 요청이 서버가 발급한 페이지에서 온 것인지 확인한다.
-
-
4.2 분산 환경에서의 세션 관리
웹 서비스의 규모가 커져 여러 대의 서버를 운영하는 분산 환경(로드 밸런싱)이 되면 세션 관리가 복잡해진다. 사용자의 첫 요청은 A 서버에서 처리되어 세션이 생성되었는데, 다음 요청이 B 서버로 전달되면 B 서버에는 해당 세션 정보가 없어서 문제가 발생한다.
이를 해결하기 위한 전략은 다음과 같다.
-
스티키 세션 (Sticky Session): 로드 밸런서가 특정 사용자의 요청을 항상 동일한 서버로 보내도록 설정하는 방식. 구현은 간단하지만, 특정 서버에 장애가 발생하면 해당 서버에 연결된 모든 사용자의 세션이 유실되는 단점이 있다.
-
세션 클러스터링 (Session Clustering): 여러 서버가 세션 정보를 서로 복제하여 공유하는 방식. 한 서버에 장애가 발생해도 다른 서버가 세션을 가지고 있어 서비스 연속성을 보장하지만, 서버 간 통신 오버헤드가 발생한다.
-
세션 스토리지 분리 (Centralized Session Store): Redis, Memcached와 같은 별도의 중앙 집중식 세션 저장소를 두는 방식. 모든 서버가 이 중앙 저장소에서 세션 정보를 읽고 쓴다. 확장성이 뛰어나고 서버가 stateless 상태를 유지할 수 있어 현대적인 아키텍처에서 가장 널리 사용되는 방식이다.
마치며 쿠키와 세션을 넘어
지금까지 웹의 상태를 기억하기 위한 위대한 여정, 쿠키와 세션에 대해 알아보았다. 이 두 기술 덕분에 우리는 편리하고 개인화된 웹 경험을 누릴 수 있게 되었다.
최근에는 세션 관리의 부담을 줄이고, 모바일 앱 등 다양한 클라이언트 환경을 지원하기 위해 **JWT(JSON Web Token)**와 같은 토큰 기반 인증 방식도 널리 사용되고 있다. JWT는 서버에 상태를 저장하지 않고, 필요한 정보를 암호화하여 토큰 자체에 담아 클라이언트와 주고받는 방식이다.
하지만 기술이 발전해도 쿠키와 세션의 기본 원리와 그들이 해결하고자 했던 문제는 변하지 않는다. 이 핸드북을 통해 쿠키와 세션의 작동 원리를 깊이 이해했다면, 앞으로 마주할 더 복잡한 웹 기술과 아키텍처를 이해하는 튼튼한 기반을 다진 것이나 다름없다. 웹의 기억을 지배하는 자가 웹 개발을 지배한다.