[Node.js] cookie와 session

Main·2024년 10월 2일
0

Node.js

목록 보기
12/20
post-thumbnail

HTTP 프로토콜은 비연결지향과 상태정보 유지 안함 이라는 특징을 가지고 있어, 서버와 클라이언트가 통신시 연속적으로 이어지지 않고 한 번 통신 되면 끊어지게 되며, 상태정보가 유지되지 않게됩니다. 이러한 문제를 해결하기 위해 사용하는 것이 cookie와 session입니다.


cookies

쿠키는 사용자의 웹 브라우저에 저장되는 작은 데이터 조각입니다. 클라이언트(브라우저)와 서버 간의 상태를 유지하는 데 사용되며, HTTP는 기본적으로 상태를 유지하지 않기 때문에 이를 보완하기 위해 사용합니다. 쿠키는 사용자 정보를 저장하고 이를 서버에 전달하여 사용자를 식별하거나 사이트의 기본 설정(언어, 테마 등)을 유지하는 데 사용됩니다.


쿠키의 동작 방식

  • 서버는 사용자의 브라우저에 쿠키를 설정하고 (Set-Cookie 헤더 사용) 이후 요청마다 브라우저는 해당 쿠키를 서버에 보내게 됩니다.
  • 쿠키에는 만료 시간, 경로, 도메인, 보안 설정 같은 속성을 지정할 수 있어 데이터의 유효기간과 접근 조건을 설정할 수 있습니다.
  • 쿠키에 민감한 데이터를 저장할 경우 보안 문제가 발생할 수 있기 때문에 쿠키의 데이터는 암호화하거나 민감한 정보를 저장하지 않도록 해야 합니다.

쿠키의 특징

1 ) 클라이언트 측 저장

쿠키는 클라이언트(사용자의 웹 브라우저)에 저장됩니다. 서버는 Set-Cookie라는 HTTP 헤더를 사용해 쿠키를 클라이언트에 설정할 수 있습니다. 이렇게 설정된 쿠키는 사용자가 웹 페이지를 다시 방문할 때마다 서버에 자동으로 전송됩니다.

2 ) 작은 크기

쿠키의 크기는 일반적으로 하나당 약 4KB로 제한됩니다. 때문에 너무 큰 데이터를 쿠키에 저장하기는 어렵고, 주로 사용자 식별 정보와 같은 간단한 데이터를 저장하는 데 사용됩니다.

3 ) 만료 기간 설정

쿠키는 만료 기간(유효기간)을 설정할 수 있습니다. 만료 기간은 ExpiresMax-Age 속성으로 설정하며, 이 속성에 따라 쿠키가 자동으로 삭제됩니다.

  • 세션 쿠키(Session Cookie): 만료 기간이 설정되지 않은 경우, 브라우저 세션이 종료되면 자동으로 삭제되는 쿠키입니다.
  • 영속 쿠키(Persistent Cookie): 만료 기간이 설정되어 있어 브라우저를 닫아도 해당 기간 동안 유지됩니다.

4 ) 도메인 및 경로 제한

쿠키는 특정 도메인과 경로에서만 사용할 수 있도록 설정할 수 있습니다. 이를 통해 쿠키를 어느 서버에서 사용할 수 있는지 제어할 수 있습니다.

  • Domain: 쿠키가 어느 도메인에서 유효한지 설정합니다. 이를 통해 특정 서브도메인에서만 쿠키를 사용할 수도 있습니다.
  • Path: 쿠키가 어느 경로에서만 사용될 수 있는지를 지정합니다. 예를 들어, /user로 설정하면 /user 경로 하위의 모든 URL에서만 해당 쿠키가 유효하게 됩니다.

5 ) 보안 설정

  • HttpOnly: 이 속성을 설정하면 쿠키가 JavaScript에서 접근할 수 없게 됩니다. 이를 통해 XSS(Cross-Site Scripting) 공격에 대한 보호를 강화할 수 있습니다.
  • Secure: 이 속성이 설정된 쿠키는 HTTPS 연결을 통해서만 전송됩니다. 이를 통해 쿠키가 암호화된 상태에서 전송되므로, 네트워크 상에서의 도청 위험을 줄일 수 있습니다.
  • SameSite: 이 속성은 쿠키의 크로스 사이트 전송을 제어합니다. Strict, Lax, None과 같은 값을 가지며, CSRF(Cross-Site Request Forgery)와 같은 공격에 대한 방어에 도움이 됩니다.

6 ) 자동 전송

사용자가 설정한 쿠키는 이후 해당 도메인의 모든 HTTP 요청마다 자동으로 포함되어 서버로 전송됩니다. 이를 통해 서버는 사용자를 식별하고, 맞춤형 정보를 제공할 수 있습니다.

7 ) 사용자 제어 가능

사용자는 브라우저 설정을 통해 쿠키를 볼 수 있고, 삭제하거나 차단할 수 있습니다. 따라서 웹 애플리케이션이 쿠키에 의존해 중요한 기능을 수행할 경우, 사용자가 쿠키를 삭제하거나 차단하면 해당 기능이 제한될 수 있습니다.


쿠키의 활용 사례

  • 로그인 유지: 사용자가 로그인할 때 '로그인 유지' 옵션을 선택하면 사용자 ID 정보를 쿠키에 저장해 자동 로그인을 구현할 수 있습니다.
  • 사용자 맞춤형 설정: 언어, 테마 설정 등 사용자가 선호하는 환경 설정을 유지하는 데 쿠키를 사용합니다.
  • 광고 트래킹: 사용자의 웹 사이트 방문 기록을 바탕으로 맞춤형 광고를 제공하는 데도 쿠키가 사용됩니다.

쿠키 사용 예시

const http = require('http');

// 서버 생성
const server = http.createServer((req, res) => {
  if (req.url === '/') {
    // 'Set-Cookie' 헤더를 설정하여 쿠키 생성
    res.setHeader('Set-Cookie', 'user=tester; Max-Age=900; HttpOnly; Path=/');
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Cookie has been set directly using http module');
  } else if (req.url === '/get-cookie') {
    // 클라이언트가 보낸 쿠키를 가져오기
    const cookies = req.headers.cookie;
    if (cookies) {
      // 간단히 쿠키 문자열을 '; '로 분리하여 각 쿠키를 배열로 나누기
      const cookieArray = cookies.split('; ');
      let userCookie;

      cookieArray.forEach(cookie => {
        const [name, value] = cookie.split('=');
        if (name === 'user') {
          userCookie = value;
        }
      });

      res.writeHead(200, { 'Content-Type': 'text/plain' });
      if (userCookie) {
        res.end(`User cookie value is: ${userCookie}`);
      } else {
        res.end('No user cookie found');
      }
    } else {
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.end('No cookies sent by client');
    }
  } else {
    // 기본 응답 처리
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

// 서버 시작
server.listen(8080, () => {
  console.log('Server is running on port 8080');
});

쿠키 설정하기

  • res.setHeader('Set-Cookie', ...)를 사용하여 직접 쿠키를 설정합니다.
  • 이 예제에서는 user=tester라는 이름과 값을 가진 쿠키를 설정하고, 다음과 같은 속성을 지정했습니다:
    • Max-Age=900: 쿠키의 수명이 900초(15분)입니다.
    • HttpOnly: 이 속성을 통해 클라이언트 측 JavaScript에서 접근하지 못하게 합니다.
    • Path=/: 이 쿠키가 애플리케이션 내의 모든 경로에서 유효하도록 설정합니다.

개발자 도구의 네트워크 탭에 헤더 부분을 확인 하면 Set-Cookie 헤더에 설정한 쿠키값이 들어간 것을 확인할 수 있습니다.

쿠키 가져오기

  • req.headers.cookie를 사용해 클라이언트가 보낸 모든 쿠키를 하나의 문자열로 가져옵니다.
  • 이 문자열은 ; 로 구분된 여러 쿠키가 포함될 수 있으므로, split('; ')을 통해 각 쿠키를 개별적으로 분리하고 원하는 값을 찾습니다.

개발자 도구 애플리케이션 탭 쿠키를 확인하면 설정한 쿠키를 확인할 수 있습니다.


쿠키를 이용한 로그인 예시

const http = require('http');
const url = require('url');
const querystring = require('querystring');

// HTML 코드: 로그인 화면과 메인 화면을 위한 HTML 문자열 준비
const loginPage = `
  <html>
    <body>
      <h2>Login</h2>
      <form method="GET" action="/login">
        <label for="id">ID: </label>
        <input type="text" id="id" name="id" />
        <button type="submit">Login</button>
      </form>
    </body>
  </html>
`;

const createMainPage = (userId) => `
  <html>
    <body>
      <h2>${userId}님 안녕하세요!</h2>
      <a href="/logout">Logout</a>
    </body>
  </html>
`;

// 서버 생성
const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url);
  
  // 쿠키 가져오기
  const cookies = req.headers.cookie;
  let userId = null;

  if (cookies) {
    const cookieArray = cookies.split('; ');
    cookieArray.forEach(cookie => {
      const [name, value] = cookie.split('=');
      if (name === 'userId') {
        userId = decodeURIComponent(value);
      }
    });
  }

  // 메인 화면
  if (parsedUrl.pathname === '/' && userId) {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(createMainPage(userId));
  
  // 로그인 화면
  } else if (parsedUrl.pathname === '/' && !userId) {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(loginPage);

  // 로그인 처리
  } else if (parsedUrl.pathname === '/login') {
    const query = querystring.parse(parsedUrl.query);
    if (query.id) {
      // ID 값을 URL 인코딩 후 쿠키에 저장 (특수 문자 처리)
      const encodedId = encodeURIComponent(query.id);
      res.setHeader('Set-Cookie', `userId=${encodedId}; Max-Age=3600; HttpOnly; Path=/`);
      res.writeHead(302, { 'Location': '/' });
      res.end();
    } else {
      res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end('ID를 입력해주세요.');
    }

  // 로그아웃 처리
  } else if (parsedUrl.pathname === '/logout') {
    // 쿠키 삭제 (Max-Age를 0으로 설정하여 삭제)
    res.setHeader('Set-Cookie', 'userId=; Max-Age=0; Path=/');
    res.writeHead(302, { 'Location': '/' });
    res.end();

  // 404 Not Found
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end('Not Found');
  }
});

// 서버 시작
server.listen(8080, () => {
  console.log('Server is running on port 8080');
});

Session

세션은 서버 측에서 사용자의 상태나 데이터를 유지하기 위한 방법입니다. 사용자가 서버에 접속할 때 서버는 세션을 생성하고 이를 통해 각 사용자의 정보를 서버에 저장합니다. 세션은 로그인 정보와 같은 데이터를 저장하여 사용자가 웹사이트를 이동할 때마다 다시 로그인할 필요가 없도록 합니다.


세션의 동작 방식

  • 사용자가 서버에 처음 접속하면 서버는 고유한 세션 ID를 생성하고 이를 쿠키에 저장하여 클라이언트에게 전달합니다.
  • 클라이언트는 이후 요청마다 이 세션 ID를 서버에 전달하여, 서버는 저장된 데이터를 참조해 사용자를 식별합니다.

세션의 특징

1 ) 서버 측 저장

  • 세션 데이터는 서버 측에 저장됩니다. 각 사용자가 서버에 연결할 때 고유한 세션 ID가 생성되고, 서버는 이 세션 ID와 관련된 데이터를 유지합니다.
  • 클라이언트는 이 세션 ID만을 쿠키 형태로 유지하며, 서버로의 요청마다 세션 ID를 전송하여 서버가 사용자를 식별할 수 있도록 합니다.

2 ) 보안성

  • 세션은 클라이언트가 직접 데이터를 저장하지 않고 서버에 저장하므로, 쿠키에 민감한 정보를 저장하는 것보다 보안성이 높습니다.
  • 클라이언트는 서버에서 발행한 세션 ID를 가지고 있을 뿐, 실제 데이터는 서버 측에만 존재합니다. 따라서 XSS(Cross-Site Scripting) 공격으로부터 상대적으로 안전합니다.

3 ) 세션 ID와 쿠키의 관계

  • 세션을 사용하기 위해서는 클라이언트에 세션 ID를 저장해야 하며, 보통 이 세션 ID는 쿠키에 저장됩니다.
  • 클라이언트는 서버에 요청할 때마다 이 세션 ID를 함께 보내어 서버가 각 사용자의 상태를 식별하고 유지할 수 있게 됩니다.

4 ) 유효 기간

  • 세션은 유효 기간이 있으며, 기본적으로 사용자가 브라우저를 닫으면 만료됩니다. 다만, 서버 설정에 따라 특정 시간 동안 비활성화된 세션을 자동으로 삭제할 수 있습니다.
  • 세션은 일반적으로 사용자의 비활성화 시간이 길어지거나, 설정된 만료 시간이 경과하면 삭제됩니다.

5 ) 서버의 자원 사용

  • 세션은 서버에 저장되기 때문에, 사용자 수가 많아지면 서버의 메모리나 저장소에 부담이 될 수 있습니다.
  • 이를 해결하기 위해 많은 시스템에서 메모리 이외에 Redis, 데이터베이스 등 외부 스토리지를 세션 저장소로 사용하기도 합니다.

6 ) 사용자 별 고유성

  • 세션은 사용자가 처음 서버에 접속할 때마다 고유한 세션 ID를 발행합니다. 이 세션 ID는 사용자를 식별하기 위해 사용되며, 서버는 이 ID와 연결된 데이터를 사용하여 사용자 상태를 관리합니다.

세션의 활용 사례

  • 로그인 상태 유지: 사용자가 로그인하면 세션에 사용자 정보를 저장해 로그인을 유지할 수 있습니다.
  • 쇼핑 카트: 사용자가 쇼핑 웹사이트에서 상품을 선택하고 장바구니에 담는 과정을 서버에서 상태로 유지하기 위해 세션을 사용합니다.
  • 사용자 맞춤 설정: 사용자가 웹사이트의 언어, 테마 등 맞춤 설정을 저장할 때 세션을 통해 이러한 정보를 유지할 수 있습니다.

세션 사용 예시

const http = require('http');

// 메모리에 세션 데이터를 저장할 객체
const sessionStore = {};

// 세션 기간 (15분)
const sessionTimeout = 900000;

// 쿠키 파싱 함수
const parseCookies = (cookieHeader) => {
  const cookies = {};
  if (cookieHeader) {
    cookieHeader.split(';').forEach(cookie => {
      const [name, value] = cookie.trim().split('=');
      cookies[name] = value;
    });
  }
  return cookies;
};

// 서버 생성
const server = http.createServer((req, res) => {
  const cookies = parseCookies(req.headers.cookie);
  let sessionId = cookies['sessionId'];

  // 세션이 존재하지 않으면 새로운 세션 생성
  if (!sessionId || !sessionStore[sessionId]) {
    sessionId = new Date().getTime(); // 새로운 세션 ID로 현재 시간 설정
    sessionStore[sessionId] = {
      user: 'tester',
      createdAt: Date.now()
    };

    // Set-Cookie 헤더로 클라이언트에게 세션 ID 전달
    res.setHeader('Set-Cookie', `sessionId=${sessionId}; Max-Age=${sessionTimeout / 1000}; HttpOnly`);
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('New session has been created');
  } else {
    const session = sessionStore[sessionId];

    // 세션이 만료되었는지 확인
    if (Date.now() - session.createdAt > sessionTimeout) {
      delete sessionStore[sessionId]; // 만료된 세션 삭제
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.end('Session expired, please refresh to create a new session');
    } else {
      // 세션이 유효하면 사용자 데이터를 반환
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.end(`User session value is: ${session.user}`);
    }
  }
});

// 서버 시작
server.listen(8080, () => {
  console.log('Server is running on port 8080');
});
  • new Date()을 사용하여 현재 시간을 세션 ID로 사용합니다.
  • 새로운 세션이 생성되면 해당 세션 ID로 세션 데이터를 메모리에 저장합니다. 클라이언트는 Set-Cookie 헤더를 통해 전달된 세션 ID를 쿠키로 저장하게 됩니다.
  • 쿠키에서 세션 ID를 확인하고, 메모리의 sessionStore에서 해당 세션이 유효한지, 만료되었는지 확인합니다.
  • 세션은 15분 동안 유효하며, 만료된 세션은 서버에서 삭제됩니다. 이후 세션이 만료되었음을 클라이언트에 알리고 새로고침 시 새로운 세션이 생성됩니다.

개발자 도구의 네트워크 탭 헤더의 Set-cookie에설정한 sessionId를 확인할 수 있습니다.

개발자 도구 애플리케이션의 쿠키 탭에 생성한 sessionId 쿠키를 확인할 수 있습니다.


세션을 사용한 로그인 예시

const http = require('http');
const url = require('url');
const querystring = require('querystring');

// 세션 저장소
const sessions = {};

// HTML 페이지
const loginPage = `
  <html>
    <body>
      <h2>Login</h2>
      <form method="GET" action="/login">
        <label for="id">ID: </label>
        <input type="text" id="id" name="id" />
        <button type="submit">Login</button>
      </form>
    </body>
  </html>
`;

const createMainPage = (userId) => `
  <html>
    <body>
      <h2>${userId}님 안녕하세요!</h2>
      <a href="/logout">Logout</a>
    </body>
  </html>
`;

// 서버 생성
const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url);
  
  // 쿠키 가져오기
  const cookies = req.headers.cookie;
  let sessionId = null;

  if (cookies) {
    const cookieArray = cookies.split('; ');
    cookieArray.forEach(cookie => {
      const [name, value] = cookie.split('=');
      if (name === 'sessionId') {
        sessionId = value;
      }
    });
  }

  // 세션 ID를 통해 사용자 확인
  const session = sessionId ? sessions[sessionId] : null;

  // 메인 화면
  if (parsedUrl.pathname === '/' && session) {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(createMainPage(session.userId));

  // 로그인 화면
  } else if (parsedUrl.pathname === '/' && !session) {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(loginPage);

  // 로그인 처리
  } else if (parsedUrl.pathname === '/login') {
    const query = querystring.parse(parsedUrl.query);
    if (query.id) {
      // 새로운 세션 생성
      const newSessionId = new Date().getTime().toString(); // 고유한 세션 ID 생성 (UNIX 타임스탬프 사용)
      sessions[newSessionId] = { userId: query.id }; // 세션 저장소에 세션 정보 저장

      // 세션 쿠키 설정
      res.setHeader('Set-Cookie', `sessionId=${newSessionId}; Max-Age=3600; HttpOnly; Path=/`);
      res.writeHead(302, { 'Location': '/' });
      res.end();
    } else {
      res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end('ID를 입력해주세요.');
    }

  // 로그아웃 처리
  } else if (parsedUrl.pathname === '/logout') {
    if (sessionId && sessions[sessionId]) {
      delete sessions[sessionId]; // 세션 삭제
    }
    // 세션 쿠키 삭제
    res.setHeader('Set-Cookie', 'sessionId=; Max-Age=0; Path=/');
    res.writeHead(302, { 'Location': '/' });
    res.end();

  // 404 Not Found
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end('Not Found');
  }
});

// 서버 시작
server.listen(8080, () => {
  console.log('Server is running on port 8080');
});

쿠키와 세션의 차이점

저장 위치

  • 쿠키: 쿠키는 클라이언트(웹 브라우저)에 저장됩니다. 서버가 클라이언트에게 특정 데이터를 저장하도록 요청하면, 브라우저는 해당 데이터를 사용자의 장치에 저장하고 이후의 요청에서 그 데이터를 서버에 다시 전송합니다.
  • 세션: 세션 데이터는 서버 측에 저장됩니다. 클라이언트는 세션 ID만 쿠키로 저장하고, 실제 데이터는 서버의 메모리나 데이터베이스에 저장되어 클라이언트의 요청에 따라 참조됩니다.

보안성

  • 쿠키: 쿠키는 클라이언트 측에 저장되기 때문에 사용자가 쿠키를 직접 수정하거나 삭제할 수 있습니다. 또한 보안이 취약한 웹사이트에서는 쿠키를 탈취당할 위험이 있습니다. 민감한 정보가 포함된 쿠키는 암호화하거나, HttpOnly 속성을 설정해 JavaScript로 접근할 수 없게 해야 합니다.
  • 세션: 세션은 서버에서 관리되므로 클라이언트 측에서 직접적으로 세션 데이터를 수정할 수 없습니다. 세션 ID만 클라이언트에 전달되므로, 세션 ID가 유출되지 않는 한 보안성이 더 높습니다.

수명

  • 쿠키: 쿠키는 설정된 Max-Age 또는 Expires 속성에 따라 만료됩니다. 이 속성에 따라 쿠키는 브라우저를 닫아도 유지되거나, 정해진 시간 후에 만료될 수 있습니다.
  • 세션: 세션은 보통 일정 기간 동안 유효하며, 클라이언트가 브라우저를 닫으면 세션이 종료됩니다. 세션의 유효 시간은 서버에서 관리하며, 특정 시간 동안 활동이 없으면 만료됩니다(예: 15분 동안 활동이 없으면 세션이 만료됨).

용량 제한

  • 쿠키: 각 쿠키는 약 4KB까지 저장할 수 있으며, 브라우저마다 쿠키의 개수 제한이 있습니다(일반적으로 도메인당 약 20~50개의 쿠키 제한).
  • 세션: 세션은 서버의 메모리나 데이터베이스에 저장되기 때문에, 일반적으로 용량 제한이 없습니다. 다만 서버 리소스를 많이 사용하는 경우 성능 저하가 발생할 수 있습니다.

구분쿠키세션
저장 위치클라이언트 측(브라우저)서버 측
보안성보안에 취약(암호화 필요), 사용자가 직접 수정 가능상대적으로 안전(클라이언트는 세션 ID만 보관)
수명브라우저 닫아도 유지 가능(지속적 저장 가능)브라우저를 닫으면 보통 만료(일정 시간 후 만료)
용량 제한4KB로 제한(브라우저별 개수 제한 있음)서버에 저장, 용량 제한 없음
사용 예시로그인 상태 유지, 사용자 설정 저장로그인 인증, 중요한 상태 정보 유지
profile
함께 개선하는 프론트엔드 개발자

0개의 댓글