Skip to content

쿠키 (Cookie) & 세션 (Session) ​

쿠키는 브라우저에 저장되어 요청마다 서버로 자동 전송되는 작은 데이터이고, 세션은 사용자 정보를 서버 쪽에 저장해 두고 브라우저에는 그 정보를 찾을 열쇠(세션 ID)만 쥐여 주는 방식입니다. 둘 다 "서버가 방금 요청을 보낸 사람이 누구인지 기억하게" 만들기 위해 쓰입니다.

한눈에 보기 ​

  • HTTP는 기본적으로 상태를 기억하지 않는(stateless) 프로토콜입니다. 서버는 요청 하나하나를 서로 관계없는 새 요청으로 받습니다. 그래서 로그인을 한 번 했더라도, 다음 요청에서 "아까 그 사람"이라는 사실을 따로 알려 주지 않으면 서버는 알 수 없습니다.
  • 비유: 놀이공원에 비유해 볼 수 있습니다.
    • 쿠키는 손목에 채워 주는 팔찌입니다. 팔찌는 내가 차고 다니고, 놀이기구를 탈 때마다 직원(서버)에게 자동으로 보여 줍니다.
    • 세션은 팔찌에 적힌 번호로 직원이 안내소 장부를 찾아보는 방식입니다. 팔찌에는 번호만 있고, "자유이용권, 키 140cm 이상" 같은 실제 정보는 안내소 장부(서버)에 적혀 있습니다.
  • 그래서 세션도 결국 세션 ID를 쿠키에 담아 주고받는 경우가 대부분입니다. 쿠키와 세션은 서로 대립하는 기술이라기보다, "정보를 어디에 두느냐"의 차이로 이해하면 쉽습니다.

아래 그림처럼 서버가 페이지와 함께 쿠키를 내려 주면, 브라우저는 같은 서버에 다시 요청할 때마다 그 쿠키를 함께 내밑니다.

쿠키가 오가는 과정: 1. 브라우저가 페이지를 요청, 2. 서버가 페이지와 쿠키를 보냄, 3. 브라우저가 같은 서버에 다른 페이지를 요청할 때 쿠키를 함께 보냄

그림 출처: Wikimedia Commons (CC BY-SA 3.0)

쿠키는 어떻게 오가나요 ​

쿠키는 HTTP 헤더 두 개로 주고받습니다.

헤더방향하는 일
Set-Cookie서버 → 브라우저 (응답)"이 값을 저장해 두세요"라고 브라우저에 지시
Cookie브라우저 → 서버 (요청)저장해 둔 쿠키 중 조건이 맞는 것을 자동으로 실어 보냄

로그인 과정을 텍스트 도식으로 그려 보면 다음과 같습니다.

[1] 브라우저 → 서버   POST /login (아이디, 비밀번호)
                    서버: 비밀번호 확인 후 세션 저장소에 {abc123: 1mo} 기록

[2] 서버 → 브라우저   200 OK
                    Set-Cookie: session_id=abc123
                    브라우저: 쿠키 저장

[3] 브라우저 → 서버   GET /mypage
                    Cookie: session_id=abc123
                    서버: abc123으로 장부 조회 → 1mo

[4] 서버 → 브라우저   200 OK (1mo님의 마이페이지)

같은 흐름을 그림으로 보면 다음과 같습니다.

세션 쿠키 로그인 흐름: 로그인 요청, 서버가 세션 ID 쿠키를 돌려줌, 다음 요청에 세션 ID 쿠키를 실어 보냄, 세션이 유효한지에 따라 응답

그림 출처: MDN, Using HTTP cookies (CC BY-SA 2.5)

브라우저는 개발자가 따로 코드를 쓰지 않아도, 조건(도메인, 경로, 만료 등)이 맞으면 요청마다 쿠키를 자동으로 붙여 보냅니다. 이 "자동으로 붙는다"는 성질이 편리함의 이유이면서, 동시에 CSRF 공격이 가능한 이유이기도 합니다.

쿠키 속성 ​

Set-Cookie 헤더에는 값 말고도 여러 속성을 붙여서 쿠키의 수명과 범위, 보안 수준을 정할 수 있습니다.

Set-Cookie: session_id=abc123; Max-Age=1800; Path=/; Secure; HttpOnly; SameSite=Lax

수명: Expires와 Max-Age ​

속성의미예
Expires정해진 날짜와 시각에 만료Expires=Wed, 21 Oct 2026 07:28:00 GMT
Max-Age지금부터 몇 초 뒤에 만료Max-Age=1800 (30분)
  • 둘 다 없으면 세션 쿠키가 되어, 브라우저를 닫을 때 사라집니다. (다만 요즘 브라우저는 "이전 탭 복원" 기능 때문에 닫았다 열어도 남아 있는 경우가 있습니다.)
  • 둘 다 있으면 Max-Age가 우선합니다.
  • 쿠키를 지우고 싶으면 Max-Age=0이나 지난 날짜의 Expires를 보내면 됩니다.

범위: Domain과 Path ​

속성의미
Domain어느 도메인으로 보낼지. 생략하면 쿠키를 만든 그 호스트에만 보냄. Domain=example.com으로 지정하면 api.example.com 같은 하위 도메인에도 보냄
Path어느 경로 아래 요청에만 보낼지. Path=/admin이면 /admin, /admin/users 요청에만 붙음

범위를 넓게 잡을수록 편하지만, 쿠키가 필요 없는 곳까지 퍼져 나가므로 꼭 필요한 만큼만 지정하는 것이 좋습니다.

보안: Secure, HttpOnly, SameSite ​

속성의미막아 주는 위험
SecureHTTPS 연결일 때만 전송암호화되지 않은 통신에서 쿠키가 엿보이는 것
HttpOnlyJavaScript의 document.cookie로 읽을 수 없음XSS로 심어진 스크립트가 세션 쿠키를 훔쳐 가는 것
SameSite다른 사이트에서 시작된 요청에 쿠키를 붙일지 결정다른 사이트가 내 로그인 쿠키를 이용해 요청을 보내는 CSRF

SameSite는 값이 세 가지입니다.

값다른 사이트에서 온 요청에 쿠키를 붙이나요
Strict절대 붙이지 않음. 다른 사이트의 링크를 눌러 들어와도 처음엔 로그인이 풀린 것처럼 보일 수 있음
Lax링크 클릭 같은 일반 이동(GET)에는 붙이고, 다른 사이트에서 보내는 폼 POST나 이미지·iframe 요청에는 붙이지 않음
None항상 붙임. 대신 반드시 Secure와 함께 써야 함

크롬을 비롯한 여러 브라우저는 SameSite를 지정하지 않으면 Lax처럼 취급합니다. 그래도 브라우저마다 기본 동작이 조금씩 다르므로 직접 명시하는 편이 안전합니다.

크기 제한 ​

쿠키는 요청마다 자동으로 실려 가기 때문에 작게 유지해야 합니다. 표준(RFC 6265)은 브라우저가 쿠키 하나당 최소 4KB, 도메인당 최소 50개는 저장할 수 있어야 한다고 정해 두었고, 실제 브라우저는 이보다 조금 더 허용하는 경우가 많습니다. 쿠키가 커지면 모든 요청이 그만큼 무거워집니다.

세션은 어떻게 동작하나요 ​

  1. 사용자가 로그인하면 서버가 추측하기 어려운 임의 문자열로 세션 ID를 만듭니다.
  2. 서버는 세션 ID → 사용자 정보를 세션 저장소에 기록합니다.
  3. 세션 ID만 Set-Cookie로 브라우저에 내려 줍니다.
  4. 이후 요청마다 브라우저가 세션 ID를 보내면, 서버는 저장소에서 사용자 정보를 찾아 씁니다.
  5. 로그아웃하거나 일정 시간 동안 요청이 없으면 서버가 저장소에서 세션을 지웁니다.

세션 저장소는 어디에 두나요 ​

저장 위치장점단점
서버 메모리가장 빠르고 간단함서버를 재시작하면 모두 로그아웃됨. 서버가 여러 대면 다른 서버는 세션을 모름
데이터베이스서버 여러 대가 공유 가능, 재시작해도 유지요청마다 DB를 조회하므로 느려질 수 있음
Redis 같은 인메모리 저장소빠르면서 여러 서버가 공유 가능, TTL로 자동 만료별도 서버를 운영해야 함

서버가 여러 대로 늘어나면 "1번 서버에서 로그인했는데 2번 서버로 요청이 가서 로그인이 풀리는" 문제가 생깁니다. 그래서 실무에서는 Redis처럼 여러 서버가 함께 보는 세션 저장소를 두는 경우가 많습니다.

세션 만료 시점 ​

세션의 만료 시점은 "알 수 없다"기보다 서버가 정책으로 정합니다. 보통 두 가지를 함께 씁니다.

  • 비활동 만료: 마지막 요청 후 30분 동안 아무 요청이 없으면 만료
  • 절대 만료: 로그인 후 아무리 활발히 써도 12시간이 지나면 만료

브라우저(클라이언트) 입장에서는 서버가 세션을 언제 지웠는지 직접 볼 수 없으므로, 다음 요청을 보냈을 때 "로그인이 필요합니다" 응답을 받고서야 알게 됩니다.

쿠키 vs 세션 비교 ​

구분쿠키에 정보를 직접 저장세션 (쿠키에는 세션 ID만)
정보가 있는 곳브라우저서버 (메모리, DB, Redis 등)
저장 형식문자열 (이름=값)서버 언어의 객체 등 자유로움
만료Expires / Max-Age로 지정, 없으면 브라우저 종료 시서버 정책 (비활동 만료, 절대 만료, 로그아웃)
크기 제한쿠키당 약 4KB브라우저 쪽 제한은 없지만, 서버 저장 공간만큼 비용이 듦
서버 자원거의 쓰지 않음사용자 수만큼 저장 공간을 차지
보안사용자가 값을 보고 고칠 수 있음. 민감한 정보는 넣으면 안 됨진짜 정보는 서버에 있어 상대적으로 안전. 단, 세션 ID를 도둑맞으면 그 사람 행세가 가능
강제 로그아웃브라우저에 있는 값을 서버가 직접 지울 수 없음서버에서 세션을 지우면 즉시 무효
쓰임테마, 언어 설정, "오늘 하루 보지 않기"로그인 상태, 장바구니, 권한 정보

브라우저 저장소 한눈에 비교 ​

브라우저에 데이터를 남기는 방법은 쿠키만 있는 것이 아닙니다. 이 사이트의 다크 모드 기능도 쿠키가 아니라 localStorage에 테마를 저장합니다. 서버가 테마 값을 알 필요가 없으니, 요청마다 따라가는 쿠키보다 localStorage가 더 알맞은 선택입니다.

구분쿠키localStoragesessionStorage
용량쿠키당 약 4KB사이트(출처)당 보통 5MB 안팎사이트(출처)당 보통 5MB 안팎
서버로 자동 전송예 (요청마다)아니요아니요
유지 기간Expires/Max-Age까지, 없으면 브라우저 종료 시직접 지울 때까지 영구탭을 닫으면 삭제
공유 범위Domain/Path 조건이 맞는 요청같은 출처의 모든 탭같은 탭 안에서만
JS로 읽기document.cookie (HttpOnly면 불가)localStorage.getItem()sessionStorage.getItem()
알맞은 용도서버가 알아야 하는 값 (세션 ID 등)테마, 최근 검색어 같은 화면 설정한 탭에서 작성 중인 폼 임시 저장
  • localStorage와 sessionStorage는 JavaScript로 언제든 읽을 수 있으므로, XSS 공격을 당하면 내용이 그대로 털립니다. 로그인 토큰을 여기에 두는 것이 위험하다는 이야기가 나오는 이유입니다. 자세한 내용은 JWT 글의 저장 위치 부분에서 다룹니다.
  • 대용량 데이터나 오프라인용 데이터는 IndexedDB라는 브라우저 내장 데이터베이스를 씁니다.

코드로 감 잡기: 세션 로그인 흉내 내기 ​

Python 표준 라이브러리 http.cookies로 Set-Cookie 헤더를 만들고, 딕셔너리를 세션 저장소처럼 써서 로그인 흐름을 흉내 내 봅니다.

python
from http.cookies import SimpleCookie
import secrets

# 서버 메모리에 있는 세션 저장소 (세션 ID -> 사용자 정보)
sessions = {}

def login(username):
    session_id = secrets.token_hex(8)          # 추측하기 어려운 임의 문자열
    sessions[session_id] = {"user": username}  # 진짜 정보는 서버에만 저장
    cookie = SimpleCookie()
    cookie["session_id"] = session_id
    cookie["session_id"]["path"] = "/"
    cookie["session_id"]["httponly"] = True
    cookie["session_id"]["secure"] = True
    cookie["session_id"]["samesite"] = "Lax"
    cookie["session_id"]["max-age"] = 1800
    return cookie.output()                      # 응답 헤더 한 줄

def handle_request(cookie_header):
    cookie = SimpleCookie()
    cookie.load(cookie_header)                  # 브라우저가 보낸 Cookie 헤더 읽기
    sid = cookie["session_id"].value if "session_id" in cookie else None
    info = sessions.get(sid)
    return f"안녕하세요, {info['user']}님" if info else "로그인이 필요합니다"

# 1) 로그인 응답: 서버가 Set-Cookie 헤더를 내려줌
set_cookie = login("1mo")
print(set_cookie)

# 2) 다음 요청: 브라우저가 Cookie 헤더에 세션 ID만 실어 보냄
sid = list(sessions)[0]
print(handle_request(f"session_id={sid}"))

# 3) 쿠키 없이 요청하거나, 엉뚱한 세션 ID를 보내면
print(handle_request(""))
print(handle_request("session_id=0000000000000000"))

# 4) 로그아웃: 서버에서 세션을 지우면 같은 쿠키를 보내도 소용없음
del sessions[sid]
print(handle_request(f"session_id={sid}"))

실행 결과 (세션 ID는 실행할 때마다 달라집니다):

Set-Cookie: session_id=f9fa39f1081e11e6; HttpOnly; Max-Age=1800; Path=/; SameSite=Lax; Secure
안녕하세요, 1mo님
로그인이 필요합니다
로그인이 필요합니다
로그인이 필요합니다
  • 브라우저에 간 것은 f9fa39f1081e11e6라는 의미 없는 문자열뿐이고, "1mo"라는 정보는 서버의 sessions에만 있습니다.
  • 마지막 줄처럼 서버가 세션을 지우면, 브라우저가 같은 쿠키를 계속 보내도 로그인이 풀립니다. 세션 방식에서 강제 로그아웃이 쉬운 이유입니다.

면접에서 자주 나오는 질문 ​

Q. 쿠키와 세션의 차이는 무엇인가요? A. 쿠키는 데이터를 브라우저에 저장하고 요청마다 자동으로 보내는 방식이고, 세션은 데이터를 서버에 저장하고 브라우저에는 세션 ID만 쿠키로 주는 방식입니다. 세션이 더 안전하지만 서버 자원을 쓰고, 서버가 여러 대일 때는 세션 저장소를 공유해야 합니다.

Q. HTTP는 무상태인데 로그인은 어떻게 유지되나요? A. 서버가 로그인 후 Set-Cookie로 세션 ID나 토큰을 내려 주고, 브라우저가 이후 요청마다 Cookie 헤더로 다시 보내 주기 때문에 서버가 같은 사용자임을 알 수 있습니다.

Q. 세션 쿠키를 안전하게 만들려면 어떤 속성을 써야 하나요? A. HTTPS에서만 보내는 Secure, 스크립트가 읽지 못하게 하는 HttpOnly, 다른 사이트 요청에 붙지 않게 하는 SameSite=Lax 또는 Strict를 함께 씁니다.

Q. 서버를 여러 대로 늘리면 세션에 어떤 문제가 생기나요? A. 로그인한 서버와 다음 요청을 받은 서버가 다르면 세션을 찾지 못합니다. 같은 사용자를 같은 서버로 보내는 스티키 세션을 쓰거나, Redis 같은 공용 세션 저장소를 두거나, 서버에 상태를 두지 않는 토큰 방식(JWT)을 고려합니다.

Q. 로그인 성공 후 세션 ID를 새로 발급하는 이유는 무엇인가요? A. 공격자가 미리 알고 있는 세션 ID를 피해자에게 심어 두고, 피해자가 그 ID로 로그인하면 공격자도 같은 ID로 로그인 상태를 쓰게 되는 세션 고정(Session Fixation) 공격을 막기 위해서입니다.

정리 ​

개념한 줄 요약
무상태(stateless)HTTP 서버는 이전 요청을 기억하지 않음. 그래서 쿠키와 세션이 필요
쿠키브라우저에 저장되어 요청마다 자동 전송되는 작은 값 (팔찌)
세션정보는 서버 장부에, 브라우저에는 세션 ID만 (팔찌 번호로 장부 조회)
Set-Cookie / Cookie서버가 저장을 지시하는 응답 헤더 / 브라우저가 돌려보내는 요청 헤더
Expires / Max-Age쿠키 수명. 없으면 브라우저 종료 시 삭제
Domain / Path쿠키를 보낼 범위
Secure / HttpOnly / SameSiteHTTPS 전용 / 스크립트 접근 차단 / 다른 사이트 요청 차단
localStorage / sessionStorage서버로 자동 전송되지 않는 브라우저 저장소. 영구 / 탭 단위