#1. Cookie

1. Cookie의 개념

1) Cookie란?

  • 변수를 클라이언트(웹브라우저)에 텍스트 파일 형식으로 저장해 놓는것을 말한다.
  • 저장 주체가 백엔드일 경우 백엔드가 갖고 있는 변수 값을 프론트엔드에 보관시킨다는 의미이다.
  • 백엔드에 접속하는 개인에 따라 맞춤형 정보를 저장할 수 있기 때문에 개인화 기능 구현에 활용된다.
  • 저장 위치가 브라우저이기 때문에 프론트엔드에서도 쿠키를 저장하거나 읽어올수 있는 API가 존재한다. (같은 값을 공유 가능)
    • 하지만 보안에는 취약하다는게 단점이다.
    • 따라서 민감한 정보는 기록해서는 안된다.
  • 백엔드와 프론트엔드가 변수값을 공유할 수 있는 가장 원시적인 방법이다.

 

2) Cookie의 특성

  • 일반 변수는 프론트엔드가 페이지를 이동하면 사라진다.
  • 기본적으로 브라우저를 닫기 전 까지는 사이트 내의 모든 페이지가 공유하는 전역변수의 역할을 한다.
  • 설정에 따라 일정 유효기간 동안은 브라우저가 재시작해도 변수값을 유지할 수 있다.

 

2. Cookie 사용

1) 패키지 설치

yarn add cookie-parser
  • 데이터를 저장, 조회 할 때 암호화 처리를 해준다.
  • 이 때 암호화에 사용되는 key 문자열은 개발자가 직접 정의해야 한다.

 

2) Cookie 저장에 필요한 조건

이름 설명
maxAge 유효시간(ms단위). 설정하지 않을 경우 브라우저를 닫으면 삭제된다.
domain 유효 도메인 지정. 지정하지 않을 경우 현재 도메인
ex) 도메인이 naver.com 인 경우, naver.com으로 설정하면 해당 도메인 내의 모든 사이트가 쿠키를 공유할 수 있다.
httpOnly boolean (true/false). false인 경우 https에서도 식별 가능하다.
path 유효경로 (기본값 "/"). 특별한 경우가 아니라면 지정하지 않는다.
signed 암호화 여부.
app.use(cookieParser('암호화키'); 형식으로 암호화 키 지정한다.

 

3. Cookie값 확인 예제

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>쿠키 테스트</h1>
    <input type="text" id="user_input" placeholder="입력하세요" />
    <button type="button" id="write">쿠키 저장하기</button>
    <button type="button" id="read">쿠키 읽어오기</button>
    <button type="button" id="delete">쿠키 삭제하기</button>
    <h2 id="console"></h2>

    <script src="//cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      const backendUrl = '/cookie';

      /** 쿠키 저장하기 버튼 클릭 이벤트 */
      document.getElementById('write').addEventListener('click', async (e) => {
        const userInput = document.getElementById('user_input').value;

        try {
          // Ajax 요청 보내기 -> 백엔드가 전달한 결과값이 response.data에 저장된다.

        //   // backend가 node가 아닌 경우 post,put,delete 파라미터 전송 방법
        //   const form = new FormData();
        //   form.append('msg', userInput);
        //   const response = await axios.post(backendUrl, form);

          // backend가 node인 경우 post, put, delete 파라미터 전송 방법
          const response = await axios.post(backendUrl, { msg: userInput });

          document.getElementById('console').innerHTML = response.data;
        } catch (error) {
          const errorMsg = '[' + error.response.status + '] ' + error.response.statusText;
          alert(errorMsg);
        }
      });

      /** 쿠키 읽어오기 버튼 클릭 이벤트 */
      document.getElementById('read').addEventListener('click', async (e) => {
        try {
          // Ajax 요청 보내기 -> 백엔드가 전달한 결과값이 response.data에 저장된다.
          const { data } = await axios.get(backendUrl);

          const html = '<h2>일반값: ' + data.my_msg + '</h2><h2>암호화: ' + data.my_msg_signed + '</h2>';

          document.getElementById('console').innerHTML = html;
        } catch (error) {
          const errorMsg = '[' + error.response.status + '] ' + error.response.statusText;
          alert(errorMsg);
        }
      });

      /** 쿠키 삭제하기 버튼 클릭 이벤트 */
      document.getElementById('delete').addEventListener('click', async (e) => {
        try {
          const { data } = await axios.delete(backendUrl);
          document.getElementById('console').innerHTML = data;
        } catch (error) {
          const errorMsg = '[' + error.response.status + '] ' + error.response.statusText;
          alert(errorMsg);
        }
      });
    </script>
  </body>
</html>

 

Node.js

import express from 'express';             // express 호출
import bodyParser from 'body-parser';      // POST 파라미터 처리
import cookieParser from 'cookie-parser';  // Cookie 처리
import serveStatic from 'serve-static';    // 특정 폴더의 파일을 URL로 노출시킴

const app = express();  // express 사용
const port = 3001;      // 포트 번호 지정

/** HTML, CSS, IMG, JS 등의 정적 파일을 URL에 노출시킬 폴더 연결 */
// "http://아이피(혹은 도메인):포트번호" 이후의 경로가 router에 등록되지 않은 경로라면 static 모듈에 연결된 폴더 안에서 해당 경로를 탐색한다.
app.use('/', serveStatic('public'));

/** POST 파라미터 수신 모듈 설정, 추가되는 미들웨어들 중 가장 먼저 설정해야한다. */
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
// extended: ture -> 지속적 사용
// extended: false -> 한번만 사용
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.text());  // TEXT형식의 파라미터 수신 가능
app.use(bodyParser.json());  // JSON형식의 파라미터 수신 가능

/** 쿠키를 처리할 수 있는 객체 연결 */
// cookie-parser는 데이터를 저장, 조회 할 때 암호화 처리를 동반한다.
// 이 때 암호화에 사용되는 key문자열을 개발자가 정해야 한다.
app.use(cookieParser('test123'));

app
  .post('/cookie', (req, res, next) => {
    // POST로 전달된 파라미터 받기
    const msg = req.body.msg;

    // 일반 쿠키 저장하기 -> 유효시간을 30초로 설정
    res.cookie('my_msg', msg, {
      maxAge: 30 * 1000,
      path: '/',
    });

    // 암호화된 쿠키 저장하기 -> 유효시간을 30초로 설정
    res.cookie('my_msg_signed', msg, {
      maxAge: 30 * 1000,
      path: '/',
      signed: true
    });

    res.status(200).send('Success');
  })
  .get('/cookie', (req, res, next) => {
    // 일반 쿠키값들은 req.cookie 객체의 하위 데이터로 저장된다. (일반 데이터)
    const my_msg = req.cookies.my_msg;
    const my_msg_signed = req.signedCookies.my_msg_signed;

    const result_data = {
      my_msg: my_msg,
      my_msg_signed: my_msg_signed,
    };

    res.status(200).send(result_data);
  })
  .delete('/cookie', (req, res, next) => {
    // 저장시 domain, path 를 설정했다면 삭제시에도 동일한 값을 지정해야 한다.
    res.clearCookie('my_msg', { path: '/' });
    res.clearCookie('my_msg_signed', { path: '/' });
    res.status(200).send('clear');
  });

app.listen(port, () => {
  console.log('-----------------------------------');
  console.log('|       Start Express Server      |');
  console.log('-----------------------------------');
});

 


 

#2. Session

1. Session의 개념

1) Session이란?

  • Cookie와 마찬가지로 사이트 내의 모든 페이지가 공유하는 전연변수이다.
  • 단, 유효시간은 설정 할 수 없기 때문에 브라우저가 닫히거나 마지막 접속 이후 5분간 재접속이 없다면 자동 폐기된다.(시간 설정 가능)
  • 특정 클라이언트에게 종속된 개인화 정보를 백엔드가 직접 저장 / 관리하는 형태이다.
  • 많은 데이터를 보관 할 수 는 없지만, 쿠키보다 보안에 유리하기 때문에 로그인 정보 관리 등에 사용된다.

 

2) 패키지 설치

yarn add express-session
  • secret: 암호화 키 입력
  • resave: 세션이 초기화 되지 않더라도 새로 저장할지 여부 (일반적으로 false)
  • saveUninitialized: 세션이 저장되기 전에 기존의 세션을 초기화 상태로 만들지 여부

 

2. 로그인 상태 관리하기

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Node Login Example</h1>
    <hr />

    <form method="post" action="/session/login" id="before-login" style="display: none">
      <div>
        <label for="user_id">아이디</label>
        <input type="text" name="user_id" id="user_id" />
      </div>

      <div>
        <label for="user_pw">비밀번호</label>
        <input type="password" name="user_pw" id="user_pw" />
      </div>

      <hr />

      <button type="submit">로그인</button>
    </form>

    <div id="after-login" style="display: none">
      <h1>안녕하세요.</h1>
      <a href="#" id="logout-link">로그아웃</a>
    </div>

    <script src="//cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      // 페이지 로드시 로그인 여부 검사
      (async () => {
        try {
          const response = await axios.get('/session/login');

          // 백엔드에서 전달된 결과가 로그인 성공을 의미하는 경우
          document.getElementById('before-login').style.display = 'none';
          document.getElementById('after-login').style.display = 'block';
        } catch (error) {
          // 실패할 경우
          document.getElementById('before-login').style.display = 'block';
          document.getElementById('after-login').style.display = 'none';
        }
      })();

      document.getElementById('before-login').addEventListener('submit', async (e) => {
        e.preventDefault();

        const user_id = document.getElementById('user_id').value;
        const user_pw = document.getElementById('user_pw').value;

        try {
          // Ajax 요청 보내기 -> 백엔드가 전달한 결과값이 response.data에 저장된다.
          const response = await axios.post('/session/login', { userid: user_id, userpw: user_pw });

          // 백엔드에서 전달된 결과가 로그인 성공을 의미하는 경우
          document.getElementById('before-login').style.display = 'none';
          document.getElementById('after-login').style.display = 'block';
        } catch (error) {
          const errorMsg = '[' + error.response.status + '] ' + error.response.statusText;
          console.error(errorMsg);
          alert('로그인에 실패했습니다. 아이디나 비밀번호를 확인하세요.');
        }
      });

      document.getElementById('logout-link').addEventListener('click', (e) => {
        e.preventDefault();

        // 즉시실행 비동기 처리 함수
        (async () => {
          try {
            // Ajax 요청 보내기 -> 백엔드가 전달한 결과값이 response.data에 저장된다.
            const response = await axios.delete('/session/login');

            // 백엔드에서 전달된 결과가 로그인 성공을 의미하는 경우
            document.getElementById('before-login').style.display = 'block';
            document.getElementById('after-login').style.display = 'none';
          } catch (error) {
            const errorMsg = '[' + error.response.status + '] ' + error.response.statusText;
            console.error(errorMsg);
            alert('로그아웃에 실패했습니다. 잠시 후 다시 시도해 주세요.');
          }
        })();
      });
    </script>
  </body>
</html>

 

Node.js

import express from 'express';               // express 호출
import bodyParser from 'body-parser';        // POST 파라미터 처리
import expressSession from 'express-session' // Session 처리
import serveStatic from 'serve-static';      // 특정 폴더의 파일을 URL로 노출시킴

const app = express();  // express 사용
const port = 3001;      // 포트 번호 지정

/** HTML, CSS, IMG, JS 등의 정적 파일을 URL에 노출시킬 폴더 연결 */
// "http://아이피(혹은 도메인):포트번호" 이후의 경로가 router에 등록되지 않은 경로라면 static 모듈에 연결된 폴더 안에서 해당 경로를 탐색한다.
app.use('/', serveStatic('public'));

/** POST 파라미터 수신 모듈 설정, 추가되는 미들웨어들 중 가장 먼저 설정해야한다. */
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
// extended: ture -> 지속적 사용
// extended: false -> 한번만 사용
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.text());  // TEXT형식의 파라미터 수신 가능
app.use(bodyParser.json());  // JSON형식의 파라미터 수신 가능

/** 세션 설정 */
app.use(expressSession({
  // 암호화 키
  secret: 'test123',
  // 세션이 초기화 되지 않더라도 새로 저장할지 여부 (일반적으로 false)
  resave: false,
  // 세션이 저장되기 전에 기존의 세션을 초기화 상태로 만들지 여부
  saveUninitialized: false,
}));

app
  .post('/session/login', (req, res, next) => {
    // req로 요청 값을 대입한다.
    const id = req.body.userid;
    const pw = req.body.userpw;

    console.log('id = ' + id);
    console.log('pw = ' + pw);

    let login_ok = false;
    // 원래는 데이터베이스에서 값이 같은지를 확인해야한다.
    if(id == 'node' && pw == '1234') {
      console.log('로그인 성공');
      // 값이 같다면 login_ok 변수를 true로 변환.
      login_ok = true;
    }

    let result_code = null;
    let result_msg = null;

    if(login_ok) {
      // 세션에 값을 대입한다.
      req.session.userid = id;
      req.session.userpw = pw;
      result_code = 200;
      result_msg = 'success';
    } else {
      result_code = 403;
      result_msg = 'fail'
    };

    const json = { rt: result_msg };
    res.status(result_code).send(json);
  })
  .delete('/session/login', async (req, res, next) => {
    let result = 'success';
    let code = 200;

    try {
      // 세션 삭제
      await req.session.destroy();
    } catch (e) {
      logger.error(e.message);
      result = e.message;
      code = 500;
    }

    const json = { rt: result };
    res.status(code).send(json);
  })
  .get('/session/login', (req, res, next) => {
    const id = req.session.userid;
    const pw = req.session.userpw;

    let result_code = null;
    let result_msg = null;

    // id와 pw값이 있다면
    if(id !== undefined && pw !== undefined) {
      console.log('현재 로그인 상태입니다.');
      result_code = 200;
      result_msg = 'success';
    } else {
      console.log('현재 로그인 상태가 아닙니다.');
      result_code = 400;
      result_msg = 'fail';
    }

    const json = { rt: result_msg };
    res.status(result_code).send(json);
  });

app.listen(port, () => {
  console.log('-----------------------------------');
  console.log('|       Start Express Server      |');
  console.log('-----------------------------------');
});

 

'국비수업 > Node.js' 카테고리의 다른 글

[Node.js/express] File Upload  (0) 2022.07.04
[Node.js/express] 메일보내기  (0) 2022.07.01
[Node.js] Express  (0) 2022.06.29
[Node.js] HTTP Server  (0) 2022.06.28
[Node.js] HTTP Client  (0) 2022.06.27

+ Recent posts