프로그래밍 언어/NODE JS

쿠키와 세션 이해하기

· 코딩마이데이

서버는 미리 클라이언트에 요청자를 추정할 만한 정보를 쿠키로 만들어 보내고, 그 다음부터는 클라이언트로부터 쿠키를 받아 요청자를 파악합니다. 쿠키가 여러분이 누구인지 추적하고 있는 것입니다. 개인정보 유충 방지를 위해 쿠기를 주기적으로 지우라고 권고하는 것은 바로 이러한 아유 때문입니다.

쿠키는 요청의 헤더(Cookie)에 담겨 전송됩니다. 브라우저는 응답의 헤더(Set-Cookie)에 따라 쿠키를 저장합니다.

cookie.js

const http = require("http");

http
  .createServer((req, res) => {
    console.log(req.url, req.headers.cookie);
    res.writeHead(200, { "Set-Cookie": "mycookie=test" });
    res.end("Hello Cookie");
  })
  .listen(8083, () => {
    console.log("8083번 포트에서 서버 대기 중입니다!");
  });

 

콘솔

$ node cookie
8083번 포트에서 서버 대기 중입니다!

 

쿠키는 name=zerocho;year=1994처럼 문자열 형식으로 존재합니다. 쿠키 간에는 세미클론으로 구분됩니다.

createServer 메서드의 콜백에서는 req 객체에 담겨 있는 쿠키그를 가져옵니다. 쿠키는 req.headers.cookie에 들어 있습니다. req.headers는 요청의 헤더를 의미합니다.

응답의 헤더에 쿠키를 기록해야 하므로 res.writeHead 메서드를 사용했습니다. Set-Cookie 브라우저한테 다음과 같은 값의 쿠기를 저장하라는 의미입니다. 실제로 응답을 받은 브라우저는 mycookie=test라는 쿠키를 저장합니다.

localhost:8083에 접속합니다. req.url과 req.headers.cookie에 대한 정보를 로깅하도록 했습니다. req.url은 주소의 path와 search 부분을 알립니다.

화면에서는 Hellp Cookie가 뜨고 콘솔에서는 다음과 같은 메시지를 볼 수 있습니다.

 

콘솔

/undefined
/favico.ico { mycookie: 'test' }

 

요청을 두 개를 통해 우리는 서버가 제대로 쿠키를 심었음을 확인할 수 있습니다. 첫 번째 요청(/)을 보내기 전에는 브라우저가 어떠한 쿠키 정보도 가지고 있지 않습니다. 서버는 응담의 헤더에 mycookie=test라는 쿠키를 심으라고 브라우저에게 명령(Set-Cookie)했습니다. 따라서 브라우저는 쿠키를 심었고, 두 번째 요청(/favicon.ico)의 헤더에 우리가 들어 있음을 확인할 수 있습니다.

 

cookie2.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>쿠키&세션 이해하기</title>
  </head>
  <body>
    <form action="/login">
      <input name="name" id="name" placeholder="이름을 입력하세요" />
      <button id="login">로그인</button>
    </form>
  </body>
</html>

 

cookie2.js

const http = require("http");
const fs = require("fs").promises;
const url = require("url");
const qs = require("querystring");

const parseCookies = (cookie = "") =>
  cookie
    .split(";")
    .map((v) => v.split("="))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});

http
  .createServer(async (req, res) => {
    const cookies = parseCookies(req.headers.cookie);

    // 주소가 /login으로 시작하는 경우
    if (req.url.startsWith("/login")) {
      const { query } = url.parse(req.url);
      const { name } = qs.parse(query);
      const expires = new Date();
      // 쿠키 유효 시간을 현재 시간 + 5분으로 설정
      expires.setMinutes(expires.getMinutes() + 5);
      res.writeHead(302, {
        Location: "/",
        "Set-Cookie": `name=${encodeURIComponent(
          name
        )}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
      });
      res.end();

      // name이라는 쿠키가 있는 경우
    } else if (cookies.name) {
      res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
      res.end(`${cookies.name}님 안녕하세요`);
    } else {
      try {
        const data = await fs.readFile("./cookie2.html");
        res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
        res.end(data);
      } catch (error) {
        res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
        res.end(error.message);
      }
    }
  })
  .listen(8084, () => {
    console.log("8084번 포트에서 서버 대기 중입니다!");
  });

 

  • 쿠키명=쿠키값: 기본적인 쿠키의 값입니다. mycookie=test 또는 name=zerocho와 같이 설정합니다.
  • Expires=날짜: 만료 기한입니다. 이 기한이 지나면 쿠키가 제거됩니다. 기본값은 클라이언트가 종료될 때까지입니다.
  • Max-age-초: Expires와 비슷하지만 날짜 대신 초를 입력할 수 있습니다. 해당 초가 지나면 쿠키가 제거됩니다. Expires보다 우선합니다.
  • Domain=도메인명: 쿠키가 전송될 도메인을 특정할 수 있습니다. 기본값은 현재 도메인입니다.
  • Path=URL: 쿠키가 전송될 URL을 특정할 수 있습니다. 기본값은 '/'이고, 이 경우 모든 URL에서 쿠키를 전송할 수 있습니다.
  • Secure: HTTPS일 경우에만 쿠키가 전송됩니다.
  • HttpOnly: 설정 시 자바스크립트에서 쿠키에 접근할 수 있습니다. 쿠키 조작을 방지하기 위한 설정하는 것이 좋습니다.

콘솔

$ node cookie2
8084번 포트에서 서버 대기 중입니다!

 

로그인 이전

 

 

로그인 이후

 

 

새로고침을 해도 로그인이 유지됩니다. 원하는 대로 동작하기는 하지만 이 방식은 상당히 위험합니다. 현재 Application 탭에서 보이는 것처럼 쿠키가 노출되어 있습니다. 또한, 쿠키가 조작될 위험도 있습니다. 따라서 이름 같은 개인정보를 쿠키에 넣어두는 것은 적절하지 못합니다.

 

session.js

const http = require("http");
const fs = require("fs").promises;
const url = require("url");
const qs = require("querystring");

const parseCookies = (cookie = "") =>
  cookie
    .split(";")
    .map((v) => v.split("="))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});

const session = {};

http
  .createServer(async (req, res) => {
    const cookies = parseCookies(req.headers.cookie);
    if (req.url.startsWith("/login")) {
      const { query } = url.parse(req.url);
      const { name } = qs.parse(query);
      const expires = new Date();
      expires.setMinutes(expires.getMinutes() + 5);
      const uniqueInt = Date.now();
      session[uniqueInt] = {
        name,
        expires,
      };
      res.writeHead(302, {
        Location: "/",
        "Set-Cookie": `session=${uniqueInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
      });
      res.end();
    } else if (
      cookies.session &&
      session[cookies.session].expires > new Date()
    ) {
      res.writeHead(200, { "Content-Type": "text/plain; charset=urf-8" });
      res.end(`${session[cookies.session].name}님 안녕하세요`);
    } else {
      try {
        const data = await fs.readFile("./cookie2.html");
        res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
        res.end(data);
      } catch (err) {
        res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
        res.end(err.message);
      }
    }
  })
  .listen(8085, () => {
    console.log("8085번 포트에서 서버 대기 중입니다!");
  });

 

쿠키에 이름을 담아서 보내는 대신, uniqueInt라는 숫자 값을 보냈습니다. 사용자의 이름과 만료 시간은 uniqueInt 속성명 아래에 있는 session이라는 객체에 대신 저장합니다.

이제 cookie.session이 있고 만료 기한을 넘기지 않았다면 session 변수에서 사용자 정보를 가져와 사용합니다. 다른 부분은 동일합니다.

 

콘솔

$ node session
8085번 포트에서 서버 대기 중입니다!

 

로그인 이후

 

이 방식이 세션입니다. 서버에 사용자 저보를 저장하고 클라이언트와는 세션 클라이언트와는 세션 아이디로만 소통합니다. 세션 아이디는 꼭 쿠키를 사용하는 방법이 제일 간단하기 때문입니다. 세션을 위해 사용하는 쿠키를 새션 쿠키라고 부릅니다.

물론 실제 배포용 서버에서는 세션을 위와 같이 변수에 저장하지 않습니다. 서버가 멈추거나 재시작되면 메모리에 저장된 변수가 초기화되기 때문입니다. 또한, 서버의 메모리가 부족하면 세션을 저장하지 못하는 문제도 생깁니다. 그래서 보통은 레디스(Redix)나 멤캐시드(Memcached) 같은 데이터베이스에 넣어둡니다.

서비스를 새로 만들 때마다 쿠키와 세션을 직접 구현할 수는 없습니다. 게다가 지금 코드로는 쿠키를 악용한 여러 가지 위험을 방어하지도 못합니다. 위의 방식 역시 세션 아이디 값이 공개되어 있어 누출되면 다음 사람이 사용할 수 있습니다.

안전하게 사용하기 위해서는 다른 사람들이 만든 검증된 코드를 사용하는 것이 좋습니다.

'프로그래밍 언어 > NODE JS' 카테고리의 다른 글

cluster  (0) 2025.05.29
https와 http2  (0) 2025.05.26
REST와 라우팅 사용하기  (0) 2025.05.19
요청과 응답 이해하기  (0) 2025.05.16
자주 발생하는 에러들  (0) 2025.05.13