Node.js

쿠키, 세션 이해하기

쿠와와 2020. 12. 14. 14:02

쿠키의 필요성

요청에는 한 가지 단점이 있음

  • 누가 요청을 보냈는지 모름(IP 주소와 브라우저 정보 정도만 앎)
  • 로그인을 구현하면 됨
  • 쿠키와 세션이 필요

쿠키: =값의 쌍

  • name=kuwhawha
  • 매 요청마다 서버에 동봉해서 보냄
  • 서버는 쿠키를 읽어 누구인지 파악
  • 매 요청마다 서버에 동봉해서 보냄

쿠키 넣는 것을 직접 구현

  • writeHead: 요청 헤더에 입력하는 메서드
  • Set-Cookie: 브라우저에게 쿠키를 설정하라고 명령
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번 포트에서 서버 대기 중입니다!');
  });

 

req.headers.cookie: 쿠키가 문자열로 담겨있음

 

 

헤더와 본문

http 요청과 응답은 헤더와 본문을 가짐

  • 헤더는 요청 또는 응답에 대한 정보를 가짐
  • 본문은 주고받는 실제 데이터
  • 쿠키는 부가적인 정보이므로 헤더에 저장

쿠키로 나를 식별하기

쿠키에 내 정보를 입력

  • parseCookies: 쿠키 문자열을 객체로 변환
  • 주소가 /login인 경우와 /인 경우로 나뉨
  • /login인 경우 쿼리스트링으로

이름을 쿠키로 저장

  • 그 외의 경우 쿠키가 있는지 없는지 판단
    • 있으면 환영 인사
    • 없으면 로그인 페이지로 리다이렉트
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: '/', // 한글이기 때문에 밑에 처럼 URI해줘야함 // Expires 쿠키의 만료기간 // js 로 쿠키 접근 못하게 보안성
      '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 (err) {
      res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end(err.message);
    }
  }
})
  .listen(8084, () => {
    console.log('8084번 포트에서 서버 대기 중입니다!');
  });

 

Set-Cookie 시 다양한 옵션이 있음

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

-> 문제 누가 로그인 했는지 노출될 수 있음

( 중요한 정보는 서버가 가지고 있고 접근할 수 있는 키만 보내는 것이 정석 ) -> 세션 방법을 선호함

 

쿠키의 정보는 노출되고 수정되는 위험이 있음

  • 중요한 정보는 서버에서 관리하고 클라이언트에는 세션 키만 제공
  • 서버에 세션 객체(session) 생성 후, uniqueInt()를 만들어 속성명으로 사용
  • 속성 값에 정보 저장하고 uniqueInt를 클라이언트에 보냄

공부하기 귀잖다면 위의 코드는 공부하지말고 지금쓰는 코드를 공부하자. ( 문론 실무에서 사용할 정도는 아니다. )

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] = {    // html에 키만 보내주는 것임 
      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=utf-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번 포트에서 서버 대기 중입니다!');
  });

 

 

 

 

 

 

 

'Node.js' 카테고리의 다른 글

npm, package.json 패키지 관리  (0) 2020.12.16
https, http2 와 cluster  (0) 2020.12.15
REST API와 라우팅  (0) 2020.12.14
서버와 클라이언트  (0) 2020.12.13
thread_pool, 이벤트, error 처리하기  (0) 2020.12.13