Back to blog
Dec 14, 2023
6 min read

HTTP 쿠키

쿠키를 통한 클라이언트 식별과 상태 관리

HTTP 쿠키는 서버가 클라이언트의 브라우저에 저장하는 작은 데이터 조각이다. HTTP는 무상태 프로토콜이므로 요청 간 연결을 기억하지 못하는데, 쿠키를 통해 상태를 유지할 수 있다.

쿠키를 사용하면 로그인 상태를 유지할 수 있고, 사용자 맞춤 설정을 저장할 수 있다. 쿠키는 웹이 상태를 가진 애플리케이션으로 동작하게 하는 핵심 메커니즘이다.

쿠키의 동작 원리

서버가 쿠키를 설정하고, 브라우저에 저장해 매 요청마다 전송한다. 브라우저는 쿠키를 자동으로 관리한다. 개발자가 수동으로 보낼 필요가 없다.

서버는 Set-Cookie 헤더로 쿠키를 설정한다. 기본 형태는 Set-Cookie: key=value이며, 여러 속성을 추가할 수 있다.

  • Domain: 쿠키가 전송될 도메인 지정한다. Domain=example.com으로 설정하면 example.com과 모든 서브도메인(www.example.com, api.example.com)에 전송된다. 생략하면 현재 도메인에만 적용된다.

  • Path: 쿠키가 전송될 경로를 지정한다. Path=/api로 설정하면 /api, /api/users/api로 시작하는 경로에만 전송된다. 기본값은 현재 경로이며, 보통 /로 설정하여 모든 경로에서 사용한다.

  • Expires / Max-Age: 쿠키의 유효 기간 설정. Expires는 절대 시간(예: Wed, 21 Oct 2025 07:28:00 GMT), Max-Age는 상대 시간(초 단위, 예: 3600은 1시간)을 지정한다. 둘 다 생략하면 세션 쿠키가 되어 브라우저를 닫으면 삭제된다.

  • Secure: HTTPS 연결에서만 쿠키를 전송한다. HTTP로 전송되면 쿠키가 노출될 수 있으므로, 민감한 정보는 필수로 설정해야 한다.

  • HttpOnly: 브라우저에서 실행되는 JavaScript에서 쿠키에 접근하지 못하게 한다. document.cookie로 읽을 수 없어 XSS 공격을 방어한다. 세션 ID는 항상 HttpOnly로 설정해야 한다.

  • SameSite: CSRF(Cross-Site Request Forgery) 공격을 방어한다.

    • Strict: 같은 사이트에서만 쿠키 전송. 다른 사이트에서 링크를 클릭해도 쿠키가 전송되지 않는다. 보안은 강하지만 사용자 경험이 떨어질 수 있다.
    • Lax: 안전한 요청(GET)에만 쿠키 전송. 링크를 클릭하면 쿠키가 전송되지만, POST 폼 제출에는 전송되지 않는다. 보안과 편의성의 균형이 좋다.
    • None: 모든 요청에 쿠키 전송. Secure 속성이 필수다. 크로스 사이트 요청에도 쿠키를 전송하므로 OAuth, 결제 게이트웨이 등에 사용된다.

브라우저는 Cookie 헤더로 쿠키를 전송한다. 여러 쿠키는 세미콜론으로 구분되며(예: Cookie: sessionId=abc123; theme=dark; language=ko), 속성은 포함되지 않고 키-값만 전송된다.

쿠키의 실무 활용

세션 관리

쿠키의 가장 일반적인 용도는 로그인 세션 유지다. 로그인 성공 시 서버가 세션 ID를 발급하고 Set-Cookie 헤더로 전송한다. 이후 모든 요청에 Cookie 헤더에 세션 ID가 포함되어 전송되며, 서버는 세션 ID로 사용자를 식별하고 사용자 정보를 사용해 요청을 처리한다.

세션 ID는 랜덤하고 예측 불가능한 값이어야 한다. 간단한 카운터나 타임스탬프는 보안에 취약하다.

node.js 서버를 개발하는 경우 다음과 같은 방식으로 쿠키를 설정한다.

// Node.js 예시
const crypto = require("crypto");
const sessions = new Map(); // 실제로는 Redis, DB 사용

// 로그인 시 세션 생성
function createSession(userId) {
  const sessionId = crypto.randomBytes(32).toString("hex");
  sessions.set(sessionId, {
    userId,
    createdAt: Date.now(),
    expiresAt: Date.now() + 3600000, // 1시간
  });

  return sessionId;
}

// 쿠키 설정
res.setHeader(
  "Set-Cookie",
  `sessionId=${sessionId}; HttpOnly; Secure; SameSite=Lax; Max-Age=3600; Path=/`
);

// 요청에서 세션 확인
function getSession(sessionId) {
  const session = sessions.get(sessionId);
  if (!session || session.expiresAt < Date.now()) {
    return null; // 세션 만료
  }
  return session;
}

세션 데이터는 서버에 저장하고, 쿠키에는 세션 ID만 저장한다. 쿠키에 사용자 정보를 직접 저장하면 보안 위험이 있다.

쿠키 기반 세션 vs JWT 토큰

쿠키 기반 세션은 서버에 세션 데이터를 저장하고, 쿠키에는 세션 ID만 저장한다. 서버에서 즉시 세션을 무효화할 수 있어 보안이 가능하며 서버내 완전 제어가 가능하지만, 확장 시 해당 세션을 공유해야하고 서버의 저장소 시소스가 필요하다.

JWT 토큰은 토큰 자체에 정보를 포함한다. 서버 저장소가 필요 없어 확장성에 용이하지만 무효화 여렵고 토큰 크기는 제한적이다.

보안이 중요한 경우일 수록 쿠키 기반 세션이 더 적합하다.

개인화 설정

사용자의 언어, 테마 등을 쿠키에 저장하며, Max-Age=31536000과 같이 일정 주기를 설정하여 사용자 경험을 일관되게 제공한다.

추적과 분석

광고 플랫폼과 분석 도구가 서드파티 쿠키로 여러 사이트를 넘나들며 사용자 행동을 추적한다. 프라이버시 우려로 인해 Safari, Firefox가 기본적으로 차단하고, Chrome도 단계적으로 차단 예정이다.

쿠키 보안

쿠키는 보안에 취약할 수 있으므로 주의가 필요하다. 실제 공격 사례와 방어 방법을 이해해야 한다.

XSS(Cross-Site Scripting) 방어

악의적인 JavaScript가 document.cookie로 쿠키를 탈취하는 공격이다. HttpOnly 속성을 설정해 JavaScript에서 접근할 수 없어 XSS 공격을 차단해야한다. 하지만 XSS 자체를 방어하는 것이 더 중요하다. 입력 검증, 출력 인코딩, CSP(Content Security Policy)를 함께 사용해야 한다.

CSRF(Cross-Site Request Forgery) 방어

다른 사이트에서 사용자 모르게 요청을 보내는 공격이다. 예를 들어 악의적인 사이트에 은행 송금 폼이 숨겨져 있고, 사용자가 그 페이지를 방문하면 자동으로 폼이 제출되어 사용자의 쿠키가 전송되어 송금이 실행된다.

SameSite=Lax로 설정하면 다른 사이트의 POST 요청에는 쿠키가 전송되지 않는다. 하지만 GET 요청은 여전히 취약할 수 있으므로, 상태 변경 작업은 POST, PUT, DELETE를 사용해야 한다.

추가 방어: 서버가 CSRF 토큰을 생성하여 폼에 포함시키고, 쿠키에도 저장한다. 요청 시 쿠키의 토큰과 폼의 토큰을 비교하여 검증한다.

중간자 공격(MITM) 방어

HTTP 통신에서 쿠키를 가로채는 공격이다. Secure 속성을 설정하면 HTTPS에서만 쿠키를 전송하여 암호화된 채널을 보장한다. 하지만 HTTPS만으로는 부족하다. HSTS(HTTP Strict Transport Security)를 설정해 HTTP로의 강제 리다이렉트를 방지해야 한다.

세션 하이재킹 방어

쿠키가 탈취되면 세션을 가로챌 수 있다. 짧은 세션 만료시간을 설정하고 세션 ID를 재생성해 로그인 시 기존 세션 무효화하고 IP, User-Agent를 검증해 사용자와 요청 정보를 비교해야한다.

쿠키의 한계와 대응

  • 크기 제한: 쿠키 하나당 최대 4KB로 제한된다. 대응 방법은 세션 ID만 쿠키에 저장하고, 실제 데이터는 서버에 저장하거나 Web Storage를 사용한다.

  • 개수 제한: 도메인당 약 50개, 전체 약 3000개로 제한된다. 초과하면 오래된 쿠키가 삭제된다. 대응 방법은 쿠키 개수를 최소화하거나, 여러 설정을 하나의 쿠키에 JSON으로 저장한다.

  • 매 요청마다 전송: 모든 요청에 쿠키가 포함되어 대역폭을 사용한다. 대응 방법은 불필요한 쿠키 제거, 정적 리소스를 별도 도메인으로 분리, Path 속성 활용이다.

  • 보안 취약점: 제대로 설정하지 않으면 XSS, CSRF에 노출된다. 보안 속성을 반드시 설정해야 한다.

실제 문제 사례와 해결

SameSite 기본값 변경으로 인한 문제

Chrome 80(2020년)부터 SameSite의 기본값이 None에서 Lax로 변경되었다. 이로 인해 OAuth 리다이렉트, 결제 게이트웨이 등 크로스 사이트 요청에서 쿠키가 전송되지 않는 문제가 발생했다. 해결 방법은 SameSite=None; Secure를 명시적으로 설정하는 것이다. SecureSameSite=None일 때 필수다.

서브도메인 간 쿠키 공유 실패

Domain 속성을 잘못 설정하면 쿠키가 공유되지 않는다. Domain=example.com처럼 점이 없으면 일부 브라우저에서 문제가 발생할 수 있다. 올바른 설정은 Domain=.example.com처럼 점으로 시작하는 것이다.

쿠키가 설정되지 않는 경우

다음 조건을 확인해야 한다:

  1. 도메인 불일치: 쿠키를 설정하는 도메인과 현재 도메인이 다르면 설정되지 않는다
  2. HTTPS 요구: Secure 속성이 있으면 HTTPS에서만 설정된다
  3. 크기 초과: 4KB를 넘으면 설정되지 않는다
  4. 브라우저 설정: 쿠키가 차단되어 있으면 설정되지 않는다

최신 트렌드와 변화

서드파티 쿠키 차단

Safari(2017), Firefox(2019)가 서드파티 쿠키를 기본 차단했다. Chrome도 2024년부터 단계적으로 차단 예정이다.

대응 방법:

  • 퍼스트파티 쿠키 사용: 추적이 필요하면 자사 도메인에서 설정
  • 서버 측 추적: 서버 로그 기반 추적
  • Privacy Sandbox: Google의 대안 기술 (Topics API, FLEDGE 등)

SameSite 기본값 변경

Chrome 80부터 SameSite 기본값이 Lax로 변경되었다. 명시적으로 설정하는 것이 좋다.

쿠키 없는 인증

일부 서비스는 쿠키 대신 Authorization 헤더를 사용한다. 장점은 CORS 문제 없음, 모바일 앱과 통일이며, 단점은 XSS에 취약(localStorage 사용 시)하다.

HTTP 쿠키는 보안 속성을 올바르게 설정하고, 용도에 맞게 사용하면 안전하고 효율적인 웹 애플리케이션을 만들 수 있다. 최신 브라우저 정책 변화를 주시하고, 대안 기술을 검토하는 것이 중요하다.