← 개인 공부 · Network
STUDY NOTE · 개념 정리

쿠키 vs 세션 vs 캐시 차이 정리 (+ 웹 스토리지, 토큰)

Network
쿠키와 세션은 "상태를 기억하기 위한 것", 캐시는 "같은 걸 다시 받지 않기 위한 것"이다. 셋을 가르는 기준은 저장 위치, 목적, 서버로의 전송 여부다.

1. 왜 필요한가: HTTP는 무상태(stateless)다

HTTP는 요청 하나하나가 독립적이다. 서버는 방금 요청한 사람이 1초 전에 로그인한 사람인지 모른다. 그래서 "이 사용자가 누구인지" 같은 상태를 이어 붙이기 위해 쿠키와 세션이 생겼다. 반면 캐시는 상태와 관계없이, 이미 받은 응답을 재사용해서 성능을 높이려는 장치다.

2. 한눈에 비교

구분쿠키 (Cookie)세션 (Session)캐시 (Cache)
목적상태 유지상태 유지 (민감 정보)성능 향상, 트래픽 절감
저장 위치클라이언트 (브라우저)서버 (메모리, Redis, DB)브라우저, 프록시, CDN
저장 대상작은 키-값 문자열사용자 상태 (로그인 정보 등)HTTP 응답 (HTML, JS, 이미지 등)
서버로 전송조건이 맞으면 요청마다 자동 전송세션 ID만 쿠키로 전송전송하지 않음 (재검증 시 헤더만)
만료Expires / Max-Age, 없으면 브라우저 종료 시서버 타임아웃, 로그아웃Cache-Control max-age 등
용량쿠키 하나당 약 4KB서버 자원 한도 내브라우저 정책에 따름
보안사용자가 보고 바꿀 수 있음서버에 있어 상대적으로 안전개인정보 응답은 저장 금지해야 함
누가 정하나서버(Set-Cookie) 또는 JS서버서버의 응답 헤더

3. 쿠키 (Cookie)

서버가 응답 헤더 Set-Cookie로 내려주면 브라우저가 저장하고, 이후 같은 도메인으로 요청할 때 Cookie 헤더에 자동으로 실어 보낸다.

속성의미
Expires / Max-Age만료 시점. 없으면 브라우저를 닫을 때 삭제되는 세션 쿠키
Domain / Path어느 주소로 요청할 때 보낼지
HttpOnlyJS(document.cookie)로 읽기 금지 → XSS로 탈취 방지
SecureHTTPS 요청에만 전송
SameSite다른 사이트에서 시작된 요청에 보낼지 (Strict / Lax / None) → CSRF 방어
  1. SameSite=Lax는 다른 사이트에서 링크를 눌러 이동하는 GET에는 보내고, 다른 사이트의 폼 POST나 이미지·iframe 요청에는 보내지 않는다. Chrome은 지정하지 않으면 Lax로 취급한다.
  2. SameSite=None은 항상 보내며, 반드시 Secure와 함께 써야 한다.

4. 세션 (Session)

사용자 정보는 서버에 저장하고, 브라우저에는 그 정보를 찾을 수 있는 세션 ID만 쿠키로 준다.

  1. 로그인 성공 → 서버가 세션 저장소에 {세션ID: 사용자정보} 저장
  2. 응답으로 Set-Cookie: JSESSIONID=abc123; HttpOnly
  3. 이후 요청마다 브라우저가 Cookie: JSESSIONID=abc123 전송
  4. 서버는 세션 ID로 저장소를 조회해 사용자를 식별

쿠키만 쓰면 userId=42를 사용자가 43으로 바꿔 보낼 수 있다. 세션은 의미 없는 난수 ID만 주고 진짜 정보는 서버에 있으니 변조할 수 없다. 대신 세션 ID 자체를 훔치면(세션 하이재킹) 그 사용자로 행세할 수 있어서 HttpOnly, Secure, 로그인 시 세션 ID 재발급(세션 고정 공격 방지)이 필요하다.

서버가 여러 대일 때의 문제: A 서버에서 만든 세션을 B 서버는 모른다.

방법설명단점
Sticky Session로드밸런서가 같은 사용자를 같은 서버로 보냄그 서버가 죽으면 세션 유실, 부하 쏠림
Session Clustering서버끼리 세션을 복제서버가 늘수록 복제 비용 증가
세션 저장소 분리Redis 같은 공용 저장소에 세션 저장저장소가 단일 장애점이 되지 않게 구성 필요

실무에서는 보통 세 번째(Spring Session + Redis)를 쓴다.

5. 캐시 (HTTP Cache)

서버가 응답 헤더로 "이 응답을 얼마나, 어떻게 재사용해도 되는지" 알려준다.

헤더 값의미
max-age=초이 시간 동안은 서버에 묻지 않고 캐시 사용 (fresh)
no-cache저장은 하되, 쓰기 전에 매번 서버에 재검증
no-store아예 저장하지 않음 (개인정보, 결제 화면)
private브라우저에만 저장, 프록시·CDN은 저장 금지
public공유 캐시(CDN)도 저장 가능
immutable만료 전에는 재검증도 하지 않음 (해시 파일명 자산)

재검증(조건부 요청): 캐시가 만료되면 다시 다 받는 게 아니라 "바뀌었니?"라고 묻는다.

  1. 첫 응답: ETag: "v1" (또는 Last-Modified)
  2. 만료 후 요청: If-None-Match: "v1"
  3. 안 바뀌었으면 304 Not Modified (본문 없음) → 캐시 재사용
  4. 바뀌었으면 200 OK + 새 본문

6. 함께 묻는 것: 웹 스토리지

구분쿠키localStoragesessionStorage
서버 자동 전송OXX
용량약 4KB약 5MB약 5MB
수명만료 설정에 따름직접 지울 때까지탭을 닫을 때까지
공유 범위도메인 단위같은 출처의 모든 탭해당 탭만
JS 접근가능 (HttpOnly면 불가)가능가능

sessionStorage는 이름만 세션일 뿐 서버 세션과 아무 관계가 없다. 모두 JS로 읽을 수 있어 XSS에 노출되므로 토큰·개인정보보다 UI 설정 저장에 적합하다.

7. 함께 묻는 것: 세션 vs 토큰(JWT)

구분세션토큰 (JWT)
상태 저장서버가 저장 (stateful)토큰 자체에 정보 + 서명 (stateless)
서버 확장세션 공유 필요서명만 검증하면 되어 쉬움
강제 로그아웃세션 삭제로 즉시 가능만료 전까지 무효화 어려움
크기세션 ID만 오가서 작음정보가 담겨 상대적으로 큼
보완책Redis 세션 저장소짧은 Access Token + Refresh Token

8. 실제로 어떻게 적용되나

  1. 로그인 유지: 세션 방식이면 세션 ID 쿠키에 HttpOnly; Secure; SameSite=Lax를 붙인다. 쿠키는 운반 수단, 세션은 실제 정보 저장소로 두 개념이 같이 쓰이는 대표 사례다.
  2. "오늘 하루 보지 않기" 팝업: 서버가 알 필요 없는 UI 상태라 쿠키나 localStorage에 만료일과 함께 저장한다.
  3. 정적 파일 배포: app.3f9a1c.js처럼 내용 해시가 붙은 파일은 max-age=31536000, immutable, 그 파일을 가리키는 HTML은 no-cache. 배포하면 HTML만 재검증되고 바뀐 JS는 파일명이 달라 새로 받는다.
  4. 마이페이지 API 응답: 사용자별 데이터는 private 또는 no-store로 CDN에 저장되지 않게 한다. 안 그러면 다른 사람에게 내 정보가 캐시로 나갈 수 있다.

9. 자주 헷갈리는 포인트

  1. no-cache는 "캐시 금지"가 아니라 "매번 확인 후 사용"이다. 금지는 no-store.
  2. 세션도 결국 쿠키를 쓴다. 차이는 쿠키에 정보 자체를 넣느냐, 정보를 찾을 키만 넣느냐다.
  3. 쿠키는 요청마다 따라가므로 많이 넣으면 모든 요청이 무거워진다.

10. 면접 질문으로 정리

  1. Q. 쿠키와 세션의 차이는? 쿠키는 클라이언트에 저장되어 요청마다 서버로 전송되는 데이터이고, 세션은 서버에 저장되는 사용자 상태다. 세션은 식별용 세션 ID만 쿠키로 주고받아 보안상 유리하지만 서버 자원을 쓰고, 서버가 여러 대면 세션 공유가 필요하다.
  2. Q. 캐시는 쿠키·세션과 무엇이 다른가? 쿠키·세션은 상태 유지가 목적이고, 캐시는 이미 받은 응답을 재사용해 응답 속도와 트래픽을 줄이는 게 목적이다. Cache-Control, ETag 같은 HTTP 헤더로 제어한다.
  3. Q. no-cache와 no-store의 차이는? no-cache는 저장하되 쓰기 전에 서버에 재검증하고, no-store는 아예 저장하지 않는다.
  4. Q. 쿠키를 안전하게 쓰려면? HttpOnly로 JS 접근을 막고, Secure로 HTTPS에서만 보내고, SameSite로 다른 사이트 요청에 실리지 않게 해 CSRF를 막는다.
Gunmo Lee
쿠키 vs 세션 vs 캐시 차이 정리 (+ 웹 스토리지, 토큰) | Gunmo's Dev Life