9장 : HTTP
쿠키와 세션
HTTP는 무상태(Stateless) 프로토콜입니다. 서버는 각 요청을 독립적으로 처리하며, 이전 요청과의 연관성을 기억하지 않습니다. 하지만 로그인 상태를 유지하거나, 장바구니에 담은 상품을 기억하려면 상태가 필요합니다.
이 모순을 해결하기 위해 만들어진 메커니즘이 쿠키(Cookie)입니다.
무상태라면
요청1: POST /login (id=hong, pw=1234) → "로그인 성공!"
요청2: GET /my-profile → "누구세요?" (기억 못함)
요청3: POST /buy (item=책) → "누구세요?" (기억 못함)
서버 입장에서 요청1과 요청2는 전혀 관련 없는 요청
→ 상태를 유지할 방법이 필요!쿠키의 동작 원리
쿠키는 서버가 클라이언트(브라우저)에 저장하도록 보내는 작은 데이터 조각입니다.
1. 로그인 요청
Client → Server: POST /login {id: "hong", pw: "1234"}
Server → Client: 200 OK
Set-Cookie: session_id=abc123; HttpOnly; Secure
2. 이후 모든 요청 (브라우저가 자동 포함)
Client → Server: GET /my-profile
Cookie: session_id=abc123
Server: "abc123은... 홍길동이구나!" → 프로필 반환
3. 장바구니
Client → Server: POST /cart/add
Cookie: session_id=abc123
Server: "홍길동의 장바구니에 추가!"
핵심: 서버는 여전히 무상태
쿠키가 매 요청에 "나는 홍길동이야"를 증명쿠키 속성
쿠키에는 동작 방식을 제어하는 여러 속성이 있습니다. 보안과 직결되므로 정확히 이해해야 합니다.
| 속성 | 값 예시 | 기능 | 보안 영향 |
|---|---|---|---|
| Domain | .example.com | 유효 도메인 | 서브도메인 포함 여부 |
| Path | /api | 유효 경로 | 특정 경로만 전송 |
| Expires | 날짜 | 만료 시점 | 지정 안 하면 세션 쿠키 |
| Max-Age | 3600 | 유효 기간(초) | Expires보다 우선 |
| HttpOnly | (플래그) | JS 접근 차단 | XSS 방어 필수 |
| Secure | (플래그) | HTTPS만 전송 | 감청 방어 필수 |
| SameSite | Strict/Lax/None | 크로스사이트 제한 | CSRF 방어 |
사용자가 bank.com에 로그인한 상태에서
evil.com을 방문하면?
evil.com의 HTML
<form action="https://bank.com/transfer" method="POST">
<input name="to" value="hacker">
<input name="amount" value="1000000">
</form>
<script>document.forms[0].submit()</script>
→ 브라우저가 bank.com에 POST 요청을 보냄
→ Cookie: session_id=abc123 자동 포함!
→ CSRF(Cross-Site Request Forgery) 공격!
SameSite=Strict
다른 사이트에서 온 모든 요청에 쿠키 미포함
→ CSRF 완전 차단
→ 단, 외부 링크로 bank.com 접속해도 쿠키 없음 (불편)
SameSite=Lax (기본값)
탑 레벨 네비게이션(링크 클릭, GET)만 쿠키 포함
다른 사이트의 POST/iframe 등에는 미포함
→ CSRF 대부분 차단, 사용성 유지
SameSite=None
모든 크로스사이트 요청에 쿠키 포함
→ Secure 필수 (HTTPS만)
→ 서드파티 쿠키에 사용Set-Cookie: session_id=abc123;
HttpOnly; ← JS에서 document.cookie 접근 불가 (XSS 방어)
Secure; ← HTTPS에서만 전송 (감청 방어)
SameSite=Lax; ← 크로스사이트 POST 차단 (CSRF 방어)
Path=/; ← 전체 경로에서 유효
Max-Age=86400; ← 24시간 후 만료세션 기반 인증 vs 토큰 기반 인증
쿠키를 활용한 인증 방식은 크게 두 가지입니다.
서버 (세션 저장소)
┌───────────────────┐
Client ──Cookie──→│ session_id │
session_id=abc │ abc → { │
│ user: "홍길동", │
│ role: "admin" │
│ } │
│ def → {...} │
│ ghi → {...} │
└───────────────────┘
장점
* 서버가 세션 통제 가능 (강제 로그아웃 = 세션 삭제)
* 민감 정보가 서버에만 존재 (클라이언트는 ID만 가짐)
* 세션 데이터 크기 제한 없음
단점
* 서버 메모리/저장소 필요 (사용자 100만 → 100만 세션)
* 서버 확장 시 세션 공유 문제
┌──Server A──┐ 세션 abc 있음
│ │
┌──Server B──┐ 세션 abc 없음! → 로그인 풀림
해결: Redis 같은 중앙 세션 저장소 사용JWT 구조
Header.Payload.Signature
Header
┌──────────┐
│{"alg": │
│ "HS256"} │
└──────────┘
Payload
┌──────────────────┐
│{"sub":"홍길동", │
│ "role":"admin", │
│ "exp":1700000000}│
└──────────────────┘
Signature
┌──────────┐
│HMACSHA256│
│(header + │
│ payload, │
│ secret) │
└──────────┘
Base64URL 인코딩 → eyJhbGci.eyJzdWI.SflKxw...
동작
Client ──Authorization: Bearer eyJhbGci...──→ Server
Server
1. 서명 검증 (비밀키로) → 변조 확인
2. 만료 시간 확인 → 유효 기간
3. 페이로드에서 사용자 정보 추출
→ 세션 저장소 조회 불필요!
장점
* 서버 무상태 (저장소 불필요) → 수평 확장 용이
* 서버 B도 같은 비밀키면 검증 가능
단점
* 발행 후 무효화 어려움 (토큰 탈취 시 만료까지 유효)
* 페이로드 크기가 쿠키 크기 제한에 걸림
* 민감 정보 포함 시 디코딩 가능 (암호화 아님!)| 비교 항목 | 세션 기반 | 토큰 기반 (JWT) |
|---|---|---|
| 서버 저장소 | 필요 (Redis 등) | 불필요 |
| 확장성 | 세션 공유 필요 | 뛰어남 |
| 강제 로그아웃 | 세션 삭제로 즉시 가능 | 블랙리스트 필요 (어려움) |
| 보안 | 서버 관리 | 토큰 탈취 위험 |
| 크기 | 세션 ID만 (작음) | 페이로드 포함 (큼) |
| 마이크로서비스 | 서비스간 세션 공유 어려움 | 토큰 전달로 간편 |
서드파티 쿠키와 프라이버시
사용자가 shopping.com 방문
┌─ shopping.com ───────────────┐
│ 상품 목록 │
│ ┌──────────────────────────┐ │
│ │ <img src="ad.tracker.com │ │ ← 서드파티 리소스
│ │ /pixel?page=shopping"> │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
ad.tracker.com → Set-Cookie: uid=user123
사용자가 news.com 방문
┌─ news.com ───────────────────┐
│ 뉴스 기사 │
│ ┌──────────────────────────┐ │
│ │ <img src="ad.tracker.com │ │ ← 같은 서드파티
│ │ /pixel?page=news"> │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
Cookie: uid=user123 자동 전송!
→ ad.tracker.com: "user123이 shopping.com 다음에 news.com 방문했구나"
→ 맞춤 광고 제공
브라우저 대응
Safari: ITP(Intelligent Tracking Prevention) → 서드파티 쿠키 차단
Firefox: ETP(Enhanced Tracking Protection) → 추적기 쿠키 차단
Chrome: Privacy Sandbox, 서드파티 쿠키 제한 정책from http.server import HTTPServer, BaseHTTPRequestHandler
from http.cookies import SimpleCookie
from datetime import datetime
class CookieHandler(BaseHTTPRequestHandler):
"""쿠키 동작 데모 서버"""
def do_GET(self):
cookies = SimpleCookie(self.headers.get("Cookie", ""))
visit_count = int(cookies.get("visits", type("", (), {"value": "0"})).value)
visit_count += 1
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
# 안전한 쿠키 설정
self.send_header(
"Set-Cookie",
f"visits={visit_count}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400"
)
self.end_headers()
html = f"""
<h1>쿠키 데모</h1>
<p>방문 횟수: {visit_count}</p>
<p>현재 시각: {datetime.now()}</p>
<p>수신된 쿠키 헤더: {self.headers.get('Cookie', '없음')}</p>
"""
self.wfile.write(html.encode())
# HTTPServer(("localhost", 8080), CookieHandler).serve_forever()다음 절에서는 HTTP의 캐시 전략과 크로스 오리진 요청을 다루는 CORS를 살펴보겠습니다.