본문 바로가기

쿠키와 세션

쿠키의 저장·전송 조건과 SameSite 보안을 익히고 서버 세션·토큰 인증 및 서드파티 쿠키의 차이를 비교합니다.

HTTP는 무상태(Stateless) 프로토콜입니다.

각 요청은 그 자체로 해석될 수 있어야 하고, 같은 TCP 연결을 탔는지 여부가 요청의 의미를 결정하지 않습니다.

이 성질 덕분에 HTTP는 단순하고 확장하기 쉽지만, 로그인 상태나 장바구니처럼 “이전 요청의 결과”가 필요한 웹 애플리케이션은 별도의 상태 관리 장치가 필요합니다.

쿠키는 이 간격을 메우기 위해 서버와 브라우저가 합의한 작은 상태 전달 메커니즘입니다.

서버는 Set-Cookie 응답 헤더로 이름, 값, 범위, 만료, 보안 속성을 보내고, 브라우저는 이후 요청에서 조건에 맞는 쿠키만 Cookie 요청 헤더에 실어 보냅니다.


쿠키의 동작 원리

쿠키 자체가 사용자를 “증명”하는 것은 아닙니다.

보통 브라우저는 예측하기 어려운 세션 식별자를 쿠키에 저장하고, 서버는 그 식별자를 세션 저장소의 사용자 상태와 매핑합니다.

쿠키 식별자로 서버 세션을 찾는 왕복

서버가 정상 로그인 요청을 받습니다. 서버가 세션 상태와 권한을 확인합니다. 각 단계의 값과 전환 이유를 비교합니다.

HTTP · 서버 세션

브라우저는 식별자를 보내고 서버는 세션 상태를 조회한다

성공한 로그인에서 서버가 세션을 저장하고 sid 쿠키를 설정합니다. 이후 전송 조건에 맞는 쿠키가 오면 저장된 상태와 요청 권한을 확인한 뒤 응답합니다.

쿠키 식별자로 서버 세션을 찾는 왕복성공한 로그인에서 서버는 세션 상태를 저장하고 브라우저에는 sid 쿠키를 보냅니다. 이후 브라우저가 전송 조건에 맞는 sid를 보내면 서버가 저장소를 조회하고 요청 권한을 확인합니다.로그인 요청세션 저장저장 완료Set-Cookie: sidCookie: sidsid 조회사용자 상태권한 확인 후 응답브라우저앱 서버세션 저장소쿠키 식별자로 서버 세션을 찾는 왕복왼쪽 브라우저, 가운데 앱 서버, 오른쪽 세션 저장소의 시간축입니다. 위에서 아래로 로그인, 저장, 저장 완료, Set-Cookie, 다음 Cookie 요청, sid 조회, 사용자 상태 반환, 권한 확인 후 응답의 같은 여덟 메시지를 읽습니다. 세션 상태는 브라우저 쿠키가 아니라 서버 저장소에 있습니다.로그인요청세션저장저장완료Set-Cookie:sidCookie:sidsid조회사용자상태권한확인 후응답브라우저앱서버세션저장소

위에서 아래로 시간 순서가 진행하며 간격은 실제 시간이 아닙니다. 실선은 요청, 가로 점선은 반환입니다. 정상 로그인과 유효한 세션의 예시입니다. Cookie에는 세션 식별자를 싣고 사용자 상태는 저장소에 둡니다. 실제 전송 조건·만료·권한 검사는 별도로 만족해야 하며, 아래 방문 횟수 데모를 실행한 흐름은 아닙니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 로그인부터 조회와 응답까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
로그인

서버가 정상 로그인 요청을 받습니다.

  • 쿠키·서버 세션 1: browser→app
  • 인증 성공을 가정한 경로로 진행
세션과 쿠키

서버에 상태를 저장한 뒤 식별자를 보냅니다.

  • 쿠키·서버 세션 1: store user state
  • 쿠키·서버 세션 2: Set-Cookie sid
  • Set-Cookie로 sid 저장; 사용자 상태 자체는 서버에 있음
다음 요청

브라우저가 전송 조건에 맞는 쿠키를 보냅니다.

  • 쿠키·서버 세션 1: Cookie sid
  • 쿠키·서버 세션 2: Domain/Path/Secure/SameSite/만료
  • Domain·Path·Secure·SameSite·만료 적용; 보유만으로 전송을 보장하지 않음
조회와 응답

서버가 세션 상태와 권한을 확인합니다.

  • 쿠키·서버 세션 1: sid 조회→user state
  • 쿠키·서버 세션 2: 권한 확인 후 응답
  • 유효 세션의 성공 응답; 쿠키 식별자 자체가 신원 증명은 아님

쿠키 식별자로 서버 세션을 찾는 왕복

서버가 정상 로그인 요청을 받습니다. 서버가 세션 상태와 권한을 확인합니다. 각 단계의 값과 전환 이유를 비교합니다.

HTTP · 서버 세션

브라우저는 식별자를 보내고 서버는 세션 상태를 조회한다

성공한 로그인에서 서버가 세션을 저장하고 sid 쿠키를 설정합니다. 이후 전송 조건에 맞는 쿠키가 오면 저장된 상태와 요청 권한을 확인한 뒤 응답합니다.

쿠키 식별자로 서버 세션을 찾는 왕복성공한 로그인에서 서버는 세션 상태를 저장하고 브라우저에는 sid 쿠키를 보냅니다. 이후 브라우저가 전송 조건에 맞는 sid를 보내면 서버가 저장소를 조회하고 요청 권한을 확인합니다.로그인 요청세션 저장저장 완료Set-Cookie: sidCookie: sidsid 조회사용자 상태권한 확인 후 응답브라우저앱 서버세션 저장소쿠키 식별자로 서버 세션을 찾는 왕복왼쪽 브라우저, 가운데 앱 서버, 오른쪽 세션 저장소의 시간축입니다. 위에서 아래로 로그인, 저장, 저장 완료, Set-Cookie, 다음 Cookie 요청, sid 조회, 사용자 상태 반환, 권한 확인 후 응답의 같은 여덟 메시지를 읽습니다. 세션 상태는 브라우저 쿠키가 아니라 서버 저장소에 있습니다.로그인요청세션저장저장완료Set-Cookie:sidCookie:sidsid조회사용자상태권한확인 후응답브라우저앱서버세션저장소

위에서 아래로 시간 순서가 진행하며 간격은 실제 시간이 아닙니다. 실선은 요청, 가로 점선은 반환입니다. 정상 로그인과 유효한 세션의 예시입니다. Cookie에는 세션 식별자를 싣고 사용자 상태는 저장소에 둡니다. 실제 전송 조건·만료·권한 검사는 별도로 만족해야 하며, 아래 방문 횟수 데모를 실행한 흐름은 아닙니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 로그인부터 조회와 응답까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
로그인

서버가 정상 로그인 요청을 받습니다.

  • 쿠키·서버 세션 1: browser→app
  • 인증 성공을 가정한 경로로 진행
세션과 쿠키

서버에 상태를 저장한 뒤 식별자를 보냅니다.

  • 쿠키·서버 세션 1: store user state
  • 쿠키·서버 세션 2: Set-Cookie sid
  • Set-Cookie로 sid 저장; 사용자 상태 자체는 서버에 있음
다음 요청

브라우저가 전송 조건에 맞는 쿠키를 보냅니다.

  • 쿠키·서버 세션 1: Cookie sid
  • 쿠키·서버 세션 2: Domain/Path/Secure/SameSite/만료
  • Domain·Path·Secure·SameSite·만료 적용; 보유만으로 전송을 보장하지 않음
조회와 응답

서버가 세션 상태와 권한을 확인합니다.

  • 쿠키·서버 세션 1: sid 조회→user state
  • 쿠키·서버 세션 2: 권한 확인 후 응답
  • 유효 세션의 성공 응답; 쿠키 식별자 자체가 신원 증명은 아님

브라우저는 쿠키를 보낼 때 Domain, Path, Secure, SameSite, 만료 시각 같은 조건을 함께 평가합니다.

그래서 쿠키 문제를 디버깅할 때는 “쿠키 값이 있는가”뿐 아니라 “현재 요청이 쿠키의 전송 조건을 만족하는가”를 같이 봐야 합니다.


쿠키 속성

쿠키 속성은 저장 위치, 전송 범위, 수명, 스크립트 접근 여부, 크로스사이트 전송 여부를 제어합니다.

특히 인증 쿠키는 값 자체보다 스코프와 전송 조건을 좁히는 것이 중요합니다.

  • Domain — 값 예시: example.com; 의미: 쿠키를 보낼 호스트 범위; 주의할 점: 생략하면 host-only 쿠키

  • Path — 값 예시: /api; 의미: 쿠키를 보낼 URL 경로 범위; 주의할 점: 보안 경계라기보다 전송 범위 필터

  • Expires — 값 예시: HTTP date; 의미: 절대 만료 시각; 주의할 점: 둘 다 있으면 Max-Age가 우선

  • Max-Age — 값 예시: 3600; 의미: 상대 만료 시간(초); 주의할 점: 음수나 0이면 삭제 용도로 쓰임

  • HttpOnly — 값 예시: 플래그; 의미: JS의 document.cookie 접근 제한; 주의할 점: XSS 자체를 막지는 않지만 세션 탈취 피해를 줄임

  • Secure — 값 예시: 플래그; 의미: 보안 연결에서만 전송; 주의할 점: HTTPS 배포의 기본값으로 두는 편이 안전

  • SameSite — 값 예시: Lax/Strict/None; 의미: 크로스사이트 요청 전송 제어; 주의할 점: CSRF 완전 대체가 아니라 방어층 중 하나


예시: 저장된 값과 전송되는 값 https://api.example.com에서 sid=R7; Path=/api; Secure; HttpOnly; SameSite=Lax를 받았고 Domain은 생략했다고 합시다. 만료되지 않았고 브라우저 정책·요청의 credentials·SameSite 조건도 만족하는 비교입니다. 같은 호스트의 /api/cart에는 경로가 맞으므로 sid를 보낼 수 있지만 /apix에는 보낼 수 없습니다. 단순히 문자열 /api로 시작하는 모든 경로가 맞는 것이 아니라 경계의 /도 확인하기 때문입니다. www.example.com/api/cart에는 host-only 조건이 맞지 않습니다. 요청에서는 Cookie: sid=R7이라는 이름·값을 보내며, Path·Secure·HttpOnly 설정을 매번 함께 보내지는 않습니다. RFC6265의 경로와 전송 규칙에 이 조건이 나옵니다.

서버 상태까지 독립 판단하기 위 쿠키의 Path만 /로 바꾸었고 다른 조건은 그대로입니다. api.example.com/apix와 www.example.com/api/cart의 전송 범위가 각각 어떻게 될까요? api.example.com에 실제로 sid를 보냈지만 세션 저장소에서 R7을 이미 삭제했다면 쿠키가 있다는 이유로 로그인 성공을 표시해도 될까요?

경로·호스트·세션을 나누는 풀이

Path=/이면 같은 호스트의 /apix에도 경로 조건이 맞습니다. Domain을 생략한 host-only 조건은 그대로이므로 www.example.com에는 여전히 맞지 않습니다. 서버의 세션 R7이 삭제됐다면 브라우저의 쿠키가 남아 있어도 그 식별자를 유효한 로그인 상태로 취급할 수 없습니다. 실제로 살아 있는 세션에서도 해당 요청의 권한과 서버 만료를 확인합니다. 브라우저 쿠키 삭제와 서버 세션 삭제는 서로 다른 상태 변경입니다.

SameSite와 CSRF

CSRF는 공격자가 사용자의 브라우저로 피해 사이트에 요청을 보내게 만드는 공격입니다.

쿠키 기반 로그인은 브라우저가 쿠키를 자동으로 붙여 보내기 때문에, 서버가 요청의 의도와 출처를 별도로 확인하지 않으면 문제가 됩니다.

SameSite=Lax는 많은 크로스사이트 POST 경로를 줄여주지만, 안전하지 않은 GET 엔드포인트, 같은 registrable domain의 하위 도메인, 오래된 클라이언트, 클라이언트 측 CSRF 같은 경우까지 해결하지는 않습니다.

중요한 상태 변경 요청은 CSRF 토큰, Origin/Referer 검증, 안전한 메서드 설계를 함께 사용해야 합니다.


안전한 쿠키 설정 예시

인증 세션 쿠키의 값은 가능한 한 무작위 식별자로 두고, 브라우저가 그 값을 저장할 위치와 전송할 조건을 제한해야 합니다.

아래 설정은 일반적인 웹 애플리케이션의 기본 출발점입니다.

Set-Cookie: __Host-session=K8x...; Path=/; Max-Age=86400; Secure; HttpOnly; SameSite=Lax

__Host- 접두사는 Secure, Path=/, Domain 미지정을 요구하는 방향으로 동작해 쿠키 스코프를 호스트 단위로 좁히는 데 도움이 됩니다.

단, 모든 보안은 서버 검증과 함께 완성됩니다.

쿠키 속성만으로 권한 검사, 세션 만료, 재발급, 로그아웃 처리를 대신할 수는 없습니다.


세션 기반 인증 vs 토큰 기반 인증

여러 서버가 있는 환경에서는 세션 저장소를 공유하거나 sticky session, 중앙 저장소 같은 설계가 필요합니다.

토큰 기반 인증, 특히 JWT는 토큰 안에 claim을 담고 서명으로 무결성을 검증합니다.

하지만 토큰이 만료되기 전까지 살아 있다는 점은 운영상 부담이 됩니다.

즉시 로그아웃, 권한 회수, 토큰 탈취 대응이 필요하면 짧은 만료 시간, refresh token, 차단 목록, token version 같은 상태 관리가 다시 등장합니다.

HTTP · 인증 상태

서명 토큰도 즉시 회수에는 별도 상태 관리가 필요하다

일반적인 서버 세션 ID와 서명 JWT를 비교합니다. 토큰을 쿠키에 넣을 수도 있으므로 운반 방식과 토큰 형식은 별개이며, 두 방식 모두 서버의 인증·권한 검증이 필요합니다.

세션 저장소와 서명 토큰의 상태 관리
비교 기준서버 세션서명 JWT
클라이언트 값보통 세션 ID서명된 claim 묶음
서버 상태세션 저장소 필요access token 검증은 무상태 가능
강제 로그아웃세션 삭제를 후속 조회에 반영 가능만료 전 무효화에는 별도 장치 필요
권한 변경저장소의 세션/사용자 상태를 갱신기존 토큰이 낡은 권한을 들 수 있음
네트워크 크기보통 작은 식별자claim과 서명 때문에 보통 더 큼
보안 초점세션 ID 난수성, 저장소 보호, 쿠키 속성서명 알고리즘, 키 관리, iss/aud/exp 검증
클라이언트 값
서버 세션: 보통 세션 ID
서명 JWT: 서명된 claim 묶음
서버 상태
서버 세션: 세션 저장소 필요
서명 JWT: access token 검증은 무상태 가능
강제 로그아웃
서버 세션: 세션 삭제를 후속 조회에 반영 가능
서명 JWT: 만료 전 무효화에는 별도 장치 필요
권한 변경
서버 세션: 저장소의 세션/사용자 상태를 갱신
서명 JWT: 기존 토큰이 낡은 권한을 들 수 있음
네트워크 크기
서버 세션: 보통 작은 식별자
서명 JWT: claim과 서명 때문에 보통 더 큼
보안 초점
서버 세션: 세션 ID 난수성, 저장소 보호, 쿠키 속성
서명 JWT: 서명 알고리즘, 키 관리, iss/aud/exp 검증

세션 삭제·권한 변경의 반영 시점은 저장소 복제와 캐시 정책도 따릅니다. JWT 만료 전 회수에는 차단 목록·token version 등 별도 장치를 둘 수 있습니다.


서명 JWT의 claim은 서명 검증으로 위변조를 확인하는 데이터입니다. 서명 자체가 내용을 암호화하는 것은 아닙니다. JWT를 어떤 쿠키나 헤더로 운반하는가, 내용의 서명·암호화 형식은 무엇인가, 요청 권한을 어떻게 판단하는가는 서로 나누어 설계합니다. RFC7519의 JWS와 JWE 구분을 참고하세요.

서드파티 쿠키와 프라이버시

서드파티 쿠키는 현재 페이지와 다른 사이트가 설정하거나 받는 쿠키입니다.

예를 들어 쇼핑몰과 뉴스 사이트가 같은 광고 도메인의 픽셀을 포함하면, 그 광고 도메인은 여러 사이트 방문을 연결해 추적할 수 있습니다.

하지만 오늘날에는 “모든 브라우저가 서드파티 쿠키를 항상 자동 전송한다”고 설명하면 부정확합니다.

브라우저와 사용자 설정에 따라 서드파티 쿠키가 차단되거나, 파티션된 쿠키처럼 최상위 사이트별로 분리될 수 있습니다.

cookie_demo.py
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; Secure; 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()

이 코드는 방문 횟수를 쿠키로 왕복시키는 데모로, 서버 세션이나 인증 예제가 아닙니다. visits를 정수로 바꾸는 입력 검증과 쿠키 헤더의 HTML 이스케이프가 빠져 있으며, 주석의 HTTPServer는 TLS를 설정하지 않습니다. Secure 쿠키는 보안 연결 조건과 브라우저의 localhost 예외 지원을 확인해야 합니다.

방문 횟수 데모를 따라 계산하기 수신 Cookie에 visits가 없으면 기본 문자열 0을 정수로 바꾸고 1을 더해 1을 출력합니다. visits=7이면 8을 출력하고 다음 visits 쿠키도 8로 설정합니다. 이 값은 클라이언트가 보내 준 것이므로 실제로 서버에 방문한 횟수를 신뢰할 수 있게 증명하지는 않습니다. visits=abc라면 int 변환부터 실패하므로, 정상 응답과 새 쿠키를 보내기 전에 입력 검증이 필요합니다. HttpOnly를 붙였다는 사실이 서버가 받는 값까지 정수로 보장하는 것은 아닙니다. 이 읽기 연습은 원 코드의 계산을 따라가는 것이며 서버·브라우저를 실행한 결과는 아닙니다.

다음 절에서는 HTTP의 캐시 전략과 크로스 오리진 요청을 다루는 CORS를 살펴보겠습니다.