생활 코딩 강의 목록 : https://opentutorials.org/module/2026/12063
인프런 강의 목록 : https://www.inflearn.com/course/node-js-활용/unit/3586?tab=curriculum
1. 강좌소개
서버측 자바스크립트 기술, Node.js 를 소개하고 사용법을 학습해 간단한 웹 어플리케이션을 만들어 보는 nodejs 강좌입니다. 기본 javascript 와 nodejs 를 이용하기 때문에 타 언어 (PHP 나 JSP) 의 도움없이 오직 javascript 기술로만 웹 어플리케이션을 구현할 수 있는 지식을 전달해줍니다. 앞선 두 강좌에서 학습한 내용과 실습예제들을 통합해 게시물과 로그인 기능이 있는 웹어플리케이션을 직접 만들어 볼수 있는 강좌입니다.
- 선수강좌가 2개 있습니다. 아래것을 먼저 학습하신후 본 강좌를 들으시면 좀더 이해가 쉬우실거에요.
- 자바스크립트의 기초적인 문법은 설명하지 않습니다. 여러 자바스크립트에 대한 다른 수업은 >>여기<< 에서 학습 가능하세요.
- 강의에서 사용하는 소스코드 링크 링크바로가기
프로젝트 준비
1) Take-Nodejs 디렉토리 생성
2) $ cd Take-Nodejs
3) $ express .
4) 넌적스 설치 및 vscode 툴에 Nunjucks Snippets 확장패키지 설치
$ npm i nunjucks --save
5)nodemon 설치 및 package.json 변경
"scripts": { "start": "node ./bin/www", "dev": "nodemon ./bin/www", "debug": "DEBUG=myapp:* yarn start" },
6)app.js 변경
var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); const nunjucks = require('nunjucks'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); //app.set('view engine', 'jade'); app.set('view engine', 'html'); // 확장자를 html 로도 사용이 가능함. nunjucks.configure('views', { // views폴더가 넌적스파일의 위치가 됨 express: app, watch: true, }); ~
7)넌적스 레이아웃 만들기
view/layout.html
<!DOCTYPE html> <html lang="ko"> <head> <title>{{title}}</title> <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></title> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body> <header> {% block header %} {% endblock %} </header> {% block content %} {% endblock %} <footer> {% block footer %} {% endblock %} </footer> </body> </html>
routes/index.js
var express = require('express'); var router = express.Router(); /* GET home page. */ // router.get('/', function (req, res, next) { // res.render('index', { title: 'Express' }); // }); router.get('/', function (req, res, next) { res.render('index', { title: 'Nunjucks' }); // index.html에 title이라는 변수를 전달 }); module.exports = router;
views/index.html
{% extends 'layout.html' %} {% block content %} <h1>{{title}}</h1> <p>Welcome to {{title}}</p> {% endblock %}
8)실행
$ npm run dev
https://mozilla.github.io/nunjucks/
https://mozilla.github.io/nunjucks/templating.html#sort-arr-reverse-casesens-attr
소스 : https://github.com/braverokmc79/Take-Nodejs
비밀번호 보안 (Security Password)
18. Security Password 1
19. Security Password 2
https://www.npmjs.com/package/md5
$ npm i md5
API
md5(message)
- message -- String, Buffer, Array or Uint8Array
- returns String
Usage
var md5 = require('md5');
console.log(md5('message'));
This will print the following
78e731027d8fd50ed642340b7c9a63b3
var fs = require('fs'); var md5 = require('md5'); fs.readFile('example.txt', function(err, buf) { console.log(md5(buf)); });
20. Security Password 3 - salt
https://www.npmjs.com/package/sha256
const express = require('express'); const router = express.Router(); const md5 = require('md5'); const sha256 = require('sha256'); ~ router.post('/auth/login', function (req, res, next) { let user = [ { username: 'egoing', password: 'a6c44b2d30bf9a5893c099bacb94606ca119b38e1454614dc1c069e8ffa958e1', displayName: 'Egoing', salt: "@#@#$SDA%#a213" }, { username: 'test1', password: '079ec662a574122c1b10d91ab3be9ae4d9cc56e8bae1deadd3c7ee105195f027', displayName: '홍길동', salt: "#@fsa3%#@f5232" }, ] const uname = req.body.username; const pwd = req.body.password; //password :1111 console.log("req.body : ", sha256(pwd + user[1].salt)); if (uname === user[0].username && sha256(pwd + user[0].salt) === user[0].password) { //세션 저장 req.session.displayName = user[0].displayName; req.session.save(function () { res.redirect("/session/welcome"); }) } else { res.render("session/login", { error: "아이디 또는 비밀번호가 일치하지 않습니다." }); } })
21. Security Password 4 - crypto.pbkdf2
pbkdf2-password 는 구
현재 nodejs express 에서 기본적으로 제공하는 crypto.pbkdf2 사용하면 된다.
이것은 passportjs 에서도 사용하고 있다.
https://www.tutorialspoint.com/crypto-pbkdf2-method-in-node-js
https://www.passportjs.org/tutorials/google/
특별히 설치할 라이브러리 는 없으며 crypto 를 rqueire 하면 된다.
const crypto = require('crypto');
crypto.pbkdf2(pwd, user[1].salt, 100000, 64, 'sha512', (err, derivedKey) => { if (err) throw err; // Printing the derived key console.log(" derivedKey : ", derivedKey); //출력결과 <Buffer 50 c1 cc 27 d8 31 10 d7 bf 94 db d9 b6 f8 d5 a3 b7 a4 e2 09 fb 0d 98 bc 27 ef ad c1 2e c7 ee d6 f9 af 42 1a 49 dd 25 bc 77 00 f8 55 2d cd b3 5f df f5 console.log("Key Derived: ", derivedKey.toString('hex')); //해싱함수를 hex 변환 출력 결과 => 50c1cc27d83110d7bf94dbd9b6f8d5a3b7a4e209fb0d98bc27efadc12ec7eed6f9af421a49dd25bc7700f8552dcdb35fdff57077d5074f74a60fcf60c258fdaa if (uname === user[0].username && derivedKey.toString('hex') === user[0].password) { //세션 저장 req.session.displayName = user[0].displayName; req.session.save(function () { res.redirect("/session/welcome"); }) } else { res.render("session/login", { error: "아이디 또는 비밀번호가 일치하지 않습니다." }); } });
22. Security Password 5
23. Security Password 6 - register
schema.sql
CREATE TABLE `member` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(30) unique NOT NULL , `password` varchar(250) DEFAULT NULL, `salt` varchar(250) DEFAULT NULL, PRIMARY KEY (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
lib/db.js
const mysql = require('mysql'); const db = mysql.createConnection({ host: 'localhost', user: 'opentutorials', password: '1111', database: 'opentutorials' }); db.connect(); module.exports = db;
app.js
const createError = require('http-errors'); const express = require('express'); const path = require('path'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); const nunjucks = require('nunjucks'); const flash = require('connect-flash'); const app = express(); const session = require('express-session'); const MySQLStore = require('express-mysql-session')(session); const MongoDBStore = require('connect-mongodb-session')(session); const FileStore = require('session-file-store')(session); const indexRouter = require('./routes/index'); const usersRouter = require('./routes/users'); const cookieRouter = require('./routes/cookies/index'); const sessionRouter = require('./routes/session/index'); //1. FileStore 설정 // app.use(session({ // secret: 'secret key', // 암호화 // resave: false, // saveUninitialized: true, // cookie: { // httpOnly: true, // }, // store: new FileStore() // 세션 객체에 세션스토어를 적용 // })); //2. MySQLStore 설정 // session DB 저장 방식 - session 테이블이 자동 생성되고 세션이 passport의해 저장 된다. app.use(session({ secret: '12312dajfj23rj2po4$#%@#', resave: false, saveUninitialized: true, store: new MySQLStore({ host: 'localhost', port: 3306, user: 'opentutorials', password: '1111', database: 'opentutorials' }) })); //3. 몽고 MongoDBStore 설정 // app.use(session({ // secret: '12312dajfj23rj2po4$#%@#', // resave: false, // saveUninitialized: true, // store: new MongoDBStore({ // uri: 'mongodb://localhost:27017/take-ndoejs', // collection: 'sessionStore' // }) // })); app.use(flash()); ~
routes/session/index.js
const express = require('express'); const router = express.Router(); const md5 = require('md5'); const sha256 = require('sha256'); const crypto = require('crypto'); const db = require('../../lib/db'); router.get('/count', function (req, res, next) { if (req.session.count) { req.session.count++; } else { req.session.count = 1; } res.render('session/count', { msg: req.session.count }); }); router.get('/tmp', function (req, res, next) { res.json("result : " + req.session.count); }) // let user = [ // { // username: 'egoing', // password: '50c1cc27d83110d7bf94dbd9b6f8d5a3b7a4e209fb0d98bc27efadc12ec7eed6f9af421a49dd25bc7700f8552dcdb35fdff57077d5074f74a60fcf60c258fdaa', // displayName: 'Egoing', // salt: "@#@#$SDA%#a213" // }, // { // username: 'test1', // password: '079ec662a574122c1b10d91ab3be9ae4d9cc56e8bae1deadd3c7ee105195f027', // displayName: '홍길동', // salt: "#@fsa3%#@f5232" // }, // ] router.get('/auth/login', function (req, res, next) { console.log("msg : ", req.session.msg); const msg = req.session.msg; delete req.session.msg; req.session.save(function () { res.render("session/login", { msg: msg }); }); }); router.post('/auth/login', function (req, res, next) { const uname = req.body.username; const pwd = req.body.password; //password :1111 db.query("SELECT * FROM member WHERE username= ? ", [uname], function (err, results, fields) { if (err) return res.status(400).render("session/login", { error: err }); if (results.length === 0) return res.status(400).render("session/login", { error: "등록된 아이디가 없습니다." }); console.log("results[0] : ", results[0]); crypto.pbkdf2(pwd, results[0].salt, 100000, 64, 'sha512', (err, derivedKey) => { if (err) throw err; // Printing the derived key console.log(" derivedKey : ", derivedKey); //출력결과 <Buffer 50 c1 cc 27 d8 31 10 d7 bf 94 db d9 b6 f8 d5 a3 b7 a4 e2 09 fb 0d 98 bc 27 ef ad c1 2e c7 ee d6 f9 af 42 1a 49 dd 25 bc 77 00 f8 55 2d cd b3 5f df f5 console.log("Key Derived: ", derivedKey.toString('hex')); //해싱함수를 hex 변환 출력 결과 => 50c1cc27d83110d7bf94dbd9b6f8d5a3b7a4e209fb0d98bc27efadc12ec7eed6f9af421a49dd25bc7700f8552dcdb35fdff57077d5074f74a60fcf60c258fdaa if (uname === results[0].username && derivedKey.toString('hex') === results[0].password) { //세션 저장 req.session.displayName = uname; req.session.save(function () { res.redirect("/session/welcome"); }) } else { res.render("session/login", { error: "아이디 또는 비밀번호가 일치하지 않습니다." }); } }); }) }) //회원 가입 router.get('/auth/register', function (req, res, next) { res.render("session/register"); }); router.post('/auth/register', function (req, res, next) { const uname = req.body.username; const pwd = req.body.password; const pwd2 = req.body.pw2; if (pwd !== pwd2) { res.render("session/register", { error: "비밀번호와 비밀번호확인이 일치하지 않습니다." }); return; } const salt = crypto.randomBytes(64).toString('base64'); crypto.pbkdf2(pwd, salt, 100000, 64, 'sha512', (err, derivedKey) => { if (err) throw err; console.log("저장할 데이터값:", uname, derivedKey.toString('hex'), salt); db.query("INSERT INTO member (username, password ,salt) VALUES (?,?,?) ", [uname, derivedKey.toString('hex'), salt], function (err, results, fields) { if (err) { return res.status(400).render("session/login", { error: err }); } req.session.msg = "회원 가입을 축하 합니다."; res.redirect("/session/auth/login"); }); }); }); router.get("/auth/logout", function (req, res, next) { delete req.session.displayName; req.session.save(function () { res.redirect("/session/welcome"); }) }); router.get('/welcome', function (req, res, next) { res.render("session/welcome", { session: req.session }); }); module.exports = router;
views/session/register.html
{% extends '../layout.html' %} {% block content %} <h1>Login</h1> <span style="color:red">{{error}}</span> <form action="/session/auth/register" method="post"> <p><input type="text" name="username" placeholder="유저명"></p> <p><input type="password" name="password" placeholder="비밀번호"></p> <p><input type="password" name="pw2" placeholder="비밀번호 확인"></p> <p> <input type="submit"> </p> </form> {% endblock %}
views/session/login.html
{% extends '../layout.html' %} {% block content %} <h1>Login</h1> <p><a href="/session/auth/register">회원가입</a></p> <span style="color:red">{{error}}</span> <span style="color:rgb(0, 119, 255)">{{msg}}</span> <form action="/session/auth/login" method="post"> <p><input type="text" name="username" placeholder="username"></p> <p><input type="password" name="password" placeholder="password"></p> <p> <input type="submit"> </p> </form> {% endblock %}
댓글 ( 4)
댓글 남기기