안녕하세요. 훈츠입니다. 이 정리글은 생활코딩 예제를 기반으로 작성하였습니다. 패스포트 JS 다음예제인 다중 사용자를 학습 및 실습해보고 정리한 글입니다. 다시 한번 이고잉님 이하 개발자분들께 감사를 표합니다.
목 차
lowdb 데이터 베이스 추가
패스워드 암호화를 위한 bcrypt 미들웨어 추가
회원가입 UI 만들기
회원정보 저장 into 인증정보 저장
로그인 구현
접근제어 - 글쓰기 (Create)
접근제어 - 글읽기 (Read)
접근제어 - 글수정 (Update)
접근제어 - 글삭제 (Delete)
bcrypt - 비밀번호 암호화
코드 공유
lowdb 데이터 베이스 추가
JSON 형태로 데이터를 저장하는 아주아주 가벼운 데이터베이스 lowdb 입니다. 사용해본 경험으로는 그다지 어렵지않고 가볍게 사용하기에 좋은것 같습니다. 다음 데이터 베이스를 적용 했습니다.
사이트 : https://github.com/typicode/lowdb
설치법
npm install -s lowdb
샘플 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// const low = require('lowdb') const FileSync = require('lowdb/adapters/FileSync') const adapter = new FileSync('db.json') const db = low(adapter) // Set some defaults (required if your JSON file is empty) db.defaults({ posts: [], user: {}, count: 0 }) .write() // Add a post db.get('posts') .push({ id: 1, title: 'lowdb is awesome'}) .write() // Set a user using Lodash shorthand syntax db.set('user.name', 'typicode') .write() // Increment count db.update('count', n => n + 1) .write() | cs |
패스워드 암호화를 위한 bcrypt 미들웨어 추가
비밀번호를 해시값으로 변조할수있고, 그 변조된값과 일반 패스워드와 비교하는기능을 제공합니다. 패스워드를 평문으로 데이터베이스 혹은 파일에 저장하는것은 굉장히 굉장히 위험하고 잘못된 방식 입니다. 이 라이브러리를 이용하면 손쉽게 비밀번호 암호화가 가능합니다.
사이트 : https://www.npmjs.com/package/bcrypt
설치법
npm install -s bcrypt
샘플 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// const bcrypt = require('bcrypt'); const saltRounds = 10; //해킹 시도시 재시작 못하도록 하는값, 커질수록 해킹이 어려워짐. const myPlaintextPassword = '1111'; //패스워드 저장 const someOtherPlaintextPassword = '1112'; //테스트위한 패스워드 저장 bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) { // 이곳에서 암호화된 hash 값을 DB에 저장 하세요. }); // Load hash from your password DB. bcrypt.compare(myPlaintextPassword, hash, function(err, result) { // 원래 넣었떤 패스워드와 저장되어있는 hash 값을 비교 합니다. // result == true 결과값은 true 입니다. }); bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) { // 다른 패스워드와 저장되어있는 hash 값을 비교 합니다. // result == false 결과값은 false 입니다. }); | cs |
회원가입 UI 만들기
코드에 대한 설명은 하지 않도록 하겠습니다.
파일 수정 및 추가
1. ../lib/auth.js 수정
2. ../lib/template.js 수정
3. ../routes/auth.js 수정
1. ../lib/auth.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// module.exports = { IsOwner : function (request,response){ if(request.user){ //유저 유무에 따라 로그인 및 로그아웃을 판단 합니다. return true; } else { return false; } }, StatusUI : function (request,response){ var authStatusUI= `<a href="/auth/login">login</a> | <a href="/auth/register">register</a>` //register 부분 if(this.IsOwner(request,response)){ console.log(request.user.nickname); authStatusUI= `${request.user.nickname} | <a href="/auth/logout">logout</a>` } return authStatusUI; } } | cs |
2. ../lib/template.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// module.exports = { html : function (title,list,body, control, authStatusUI=`<a href="/auth/login">login</a> | <a href="/auth/register">register</a>`){ return ` <!doctype html> <html> <head> <title>WEB1 -${title}</title> <meta charset="utf-8"> </head> <body> ${authStatusUI} <h1><a href="/">WEB</a></h1> ${list} ${control} ${body} </body> </html> `; }, list : function (filelist){ var list = `<ul>`; var i = 0; while(i < filelist.length){ list = list + `<li><a href="/topic/${filelist[i].id}">${filelist[i].title}</a></li>`; i = i+ 1; } list = list +`</ul>`; return list; } } | cs |
3. ../routes/auth.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// //REGISTER router.get('/register', function(request, response){ //플래시 메시지 request.flash 로 들어옵니다. var fmsg = request.flash(); console.log('flash',fmsg); var feedback ='' if(fmsg.error){ feedback = fmsg.error[0] } var filelist = request.filelist; var title = 'WEB - register'; var list = template.list(filelist) var html = template.html(title,list,` <div style="color:red">${feedback}<div> <form action="/auth/register_process" method="POST"> <p><input type="text" name="email" placeholder="email"></p> <p><input type="password" name="password" placeholder="password"></p> <p><input type="password" name="password2" placeholder="password2"></p> <p><input type="text" name="nickname" placeholder="nickname"></p> <p><input type="submit" vlaue="register"></p> </form> `,''); response.send(html); }); | cs |
회원정보 저장 into 세션
기존에 file로 저장 하던 부분을 lowdb를 설치하고 JSON 파일안에 데이터를 저장 하도록 변경 하였습니다.
순서
1. lowdb 설치 및 사용설정 (이전 글 참조)
2. shortid 설치 및 사용설정
3. main.js 수정
4. auth.js - register_process 수정 / 세션에 저장
1. lowdb 설치 및 사용설정 (이전 글 참조)
1 2 3 4 5 6 7 8 9 10 11 12 13 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// const low = require('lowdb') const FileSync = require('lowdb/adapters/FileSync'); const adapter = new FileSync('db.json') const db = low(adapter) db.defaults({users:[], topics:[], hashs:''}).write(); module.exports = db; //외부에서 사용 가능하도록 | cs |
2. shortid 설치 및 사용설정
npm install -s shortid
1 2 3 4 5 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// const shortid = require('shortid'); var id = shortid.generate(); //자동으로 유니크한 id 값을 생성합니다. | cs |
3. main.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// //미들웨어 만들어 사용하기 //타이틀 리스트 app.get('*',function(req,res, next){ req.filelist = db.get('topics').value(); next(); //기존 파일 리스트 읽는코드 /* fs.readdir('./data', function(err, filelist){ req.filelist = filelist; next(); }); */ }); | cs |
4. auth.js - register_process 수정 / 세션에 저장 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// //REGISTER PROCESS Method is POST router.post('/register_process', function (request, response) { var post = request.body; var email = post.email; var password = post.password; var password2 = post.password2; var nickname = post.nickname; //코드 생략~~ const user = { id : shortid.generate(), //short id 생성 email:email, password:password, nickname:nickname } // lowDB 에 저장 db.get('users').push(user).write(); //로그인 세션에 넘겨준 데이터가 저장됩니다. request.login(user, function(err){ console.log('login suecess') response.redirect('/'); }) }); } }); return router; } | cs |
로그인 구현
패스포트.js 파일을 수정합니다. 기존에 authData를 세션에 저장되어있는 user 객체를 이용하여 id 와 password를 검증합니다.
passport.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// module.exports = function(app){ const db = require('../lib/lowdb') //패스포트 로컬 전략 var passport = require('passport'), LocalStrategy = require('passport-local').Strategy; //passport session 사용 app.use(passport.initialize()); app.use(passport.session()); //패스포트 사용 passport.use(new LocalStrategy( //사용자가 필드를 변경시키고 싶으면 아래처럼 추가 합니다. { usernameField: 'email', passwordField: 'password' }, function(email, password, done) { console.log('LocalStrategy ' + email, password) user = db.get('users').find({email:email}).value(); if(email === user.email){ console.log('1'); if(password === user.password){ console.log('2'); return done(null, user); //아이디와 비번이 일치할때, done 함수로 유저데이터를 넣어주면 session에 등록됩니다. }else{ console.log('3'); return done(null, false, { message: 'Incorrect password.' }); } }); } else { console.log('4'); return done(null, false, { message: 'Incorrect username.' }); } } )); //Passport 로그인이 성공되면, 그곳에서 입력한 데이터가 이곳에 콜백의 유저로 넘어 옵니다. passport.serializeUser(function(user, done) { console.log('serializeUser :' , user.id); done(null, user.id); }); //한번 통신하고 나면,웹이 리로드될때마다 이곳이 호출되어서 이곳에서 DB의 유저값을가져와 비교합니다. //심플한 테스트를 위해,db 에 있는값이 아닌 위의 값을 넣어줍니다. passport.deserializeUser(function(id, done) { var user = db.get('users').find({id:id}).value(); console.log('deserializeUser :',id,user); done(null, user); }); return passport; } | cs |
접근제어 - 글쓰기 (Create)
글쓰기 할때, 로그인한 당사자의 id 값을 저장 합니다. 이후에 읽기, 수정 그리고 삭제 할때 확인용으로 id 값이 필요 합니다.
router.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// //이전 코드생략 //CREATE PROCESS Method is POST router.post('/create_process', function (request, response) { var post = request.body; var title = post.title; var description = post.description; var id = shortid.generate(); //글제목 및 내용을 LOWDB에 합니다. db.get('topics').push({id:id,title:title,description:description,user_id:request.user.id}).write(); //shortid 로 자동생성된 id 값으로 페이지를 리다이렉션 합니다. response.redirect(`/topic/${id}`); }); //WEB PAGE load router.get('/:pageId', function(request, response, next){ //형식은 'user내가 치는거대로+ Id' ex page를 쳤다면 pageId //pageId 에 들어오는 값은 : request.params.pageId 이 형식에 유의 하고, param 이라고 치지 않도록 유의!!! var topic = db.get('topics').find({id:request.params.pageId}).value(); var user = db.get('users').find({id:topic.user_id}).value(); var filelist = request.filelist; //main에 미들웨어로 넣어놓았음. var title = topic.title; var descriton = topic.description; var writer = user.nickname; //글쓴이를 표현 해줍니다. var create = loginCheckUI('create','','',request,response); var update = loginCheckUI('update',topic,'',request,response); var deletes = loginCheckUI('deletes',topic,'',request,response); var list = template.list(filelist) var html = template.html(title,list, `<h2>${title}</h2>${descriton}<br>edit by ${writer}`, `${create} ${update} ${deletes} `, auth.StatusUI(request,response) ); response.send(html); }); | cs |
접근제어 - 글읽기 (Read)
적은 글을 읽기 위해서는 기존 readFile 함수를 모두 lowdb 의 get 함수로 바꿔서 읽어 줘야 합니다. 다음과 같은 부분을 수정 합니다. main.js 파일리스트를 넣어주는 미들웨어 부분은 이전에 변경 해두었으니, 위쪽에서 확인해보세요.
1. template.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// module.exports = { html : function (title,list,body, control, authStatusUI=`<a href="/auth/login">login</a> | <a href="/auth/register">register</a>`){ return ` <!doctype html> <html> <head> <title>WEB1 -${title}</title> <meta charset="utf-8"> </head> <body> ${authStatusUI} <h1><a href="/">WEB</a></h1> ${list} ${control} ${body} </body> </html> `; }, list : function (filelist){ var list = `<ul>`; var i = 0; while(i < filelist.length){ list = list + `<li><a href="/topic/${filelist[i].id}">${filelist[i].title}</a></li>`; //이부분 수정 i = i+ 1; } list = list +`</ul>`; return list; } } | cs |
접근제어 - 글수정 (Update)
router.js 에 update 부분과 update_process 부분을 수정 합니다.
router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// //UPDATE get router.get('/update/:pageId', function(request, response){ var topic = db.get('topics').find({id:request.params.pageId}).value(); //topic 정보 가져오기 var title = topic.title; //topic 에 title var description = topic.description; //topic 에 description var filelist = request.filelist; //main에 미들웨어에 filelist 정보 가져오기 var list = template.list(filelist) var html = template.html(title,list, ` <form action="/topic/update_process" method="post"> <input type="hidden" name="id" value="${topic.id}"> <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="/topic/create">create</a> <a href="/topic/update/${topic.id}">update</a> ` , auth.StatusUI(request,response) ); response.send(html); }); //UPDATE PROCESS Method POST router.post('/update_process', function(request, response){ var post = request.body; var id = post.id // update 에서 var title = post.title; var description = post.description; var topic = db.get('topics').find({id:id}).value(); if(topic.user_id !== request.user.id){ request.flash('error is not yours'); response.redirect('/'); return false; } db.get('topics').find({id:id}).assign({ title:title, description:description }).write(); response.redirect(`/topic/${topic.id}`); }); | cs |
접근제어 - 글삭제 (Delete)
글을 삭제 하기위해서, delete process 와 글을 읽고 이동하는 부분에서 login check를 확인하는 펑션에 lowdb 에 있는 id값을 받을수 있도록 수정 했습니다.
router.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// //DELETE Methode is POST router.post('/delete_process', function (request, response){ var post = request.body; var id = post.id; //delete 로 부터 받은 id 값 var topic = db.get('topics').find({id:id}).value(); if(topic.user_id !== request.user.id){ response.redirect('/'); return false; } db.get('topics').remove({id:id}).write(); response.redirect('/'); }); //login 체크 펑션 function loginCheckUI(mode,topic,descriton,request,response){ if(mode === 'create'){ if(auth.IsOwner(request,response)){ return create = `<a href="/topic/create">create</a>`; } else {return ''} } if(mode === 'update'){ if(auth.IsOwner(request,response)){ return update = `<a href="/topic/update/${topic.id}">update</a>`; //topic.id 로 변경 } else {return ''} } if(mode === 'deletes'){ if(auth.IsOwner(request,response)){ return deletes = `<form action="/topic/delete_process" method="post"> <input type="hidden" name="id" value="${topic.id}"> //topic.id 로 변경 <input type="submit" value="delete"> </form>`; } else {return ''} } } //WEB PAGE load router.get('/:pageId', function(request, response, next){ //형식은 'user내가 치는거대로+ Id' ex page를 쳤다면 pageId var topic = db.get('topics').find({id:request.params.pageId}).value(); var user = db.get('users').find({id:topic.user_id}).value(); var filelist = request.filelist; //main에 미들웨어로 넣어놓았음. var title = topic.title; var descriton = topic.description; var writer = user.nickname; var create = loginCheckUI('create','','',request,response); var update = loginCheckUI('update',topic,'',request,response); var deletes = loginCheckUI('deletes',topic,'',request,response); var list = template.list(filelist) var html = template.html(title,list, `<h2>${title}</h2>${descriton}<br>edit by ${writer}`, `${create} ${update} ${deletes} `, auth.StatusUI(request,response) ); response.send(html); }); | cs |
bcrypt - 비밀번호 암호화
bcrypt 설정과 사용법은 글의 처음을 참조하세요. 적용 내용만 업데이트 합니다.
bcrypt 를 이용하여, password를 hash로 받고 lowdb에 저장합니다.
auth.js 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// const bcrypt = require('bcrypt'); const saltRounds = 10; //REGISTER PROCESS Method is POST router.post('/register_process', function (request, response) { var post = request.body; var email = post.email; var password = post.password; var password2 = post.password2; var nickname = post.nickname; //코드 생략~ //bcrypt 를 이용하여, password를 넣으면 콜백으로 hash 값을 받습니다. bcrypt.hash(password, saltRounds, function(err, hash) { // Store hash in your password DB. const user = { id : shortid.generate(), email:email, password:hash, // 콜백의 hash 값을 user password 에 저장합니다. nickname:nickname } db.get('users').push(user).write(); //lowdb 에 저장 합니다. console.log('5'); // passport 를 이용하여, 세션에 로그인 유저데이터 저장 request.login(user, function(err){ console.log('login suecess') response.redirect('/'); }) }); } }); | cs |
패스포트JS 에서 로그인 체크 하는 부분에서 hash 값으로 저장된 패스워드와 입력으로 들어온 패스워드를 bcrypt.compare 블럭을 이용하여 체크 합니다.
passport.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | //Hoons Blog---https://rain2002kr.tistory.com------------------------------------------------------------------코드/// module.exports = function(app){ const db = require('../lib/lowdb') const bcrypt = require('bcrypt'); //패스포트 로컬 전략 var passport = require('passport'), LocalStrategy = require('passport-local').Strategy; //passport session 사용 app.use(passport.initialize()); app.use(passport.session()); //패스포트 사용 passport.use(new LocalStrategy( //사용자가 필드를 변경시키고 싶으면 아래처럼 추가 합니다. { usernameField: 'email', passwordField: 'password' }, function(email, password, done) { console.log('LocalStrategy ' + email, password) user = db.get('users').find({email:email}).value(); if(email === user.email){ //bcrypt 의 compare 블럭으로 입력된 password 와 저장된 hash password를 비교 합니다. bcrypt.compare(password, user.password, function(err, result) { if(result){ return done(null, user); //아이디와 비번이 일치할때, done 함수로 유저데이터를 넣어주면 session에 등록됩니다. }else{ return done(null, false, { message: 'Incorrect password.' }); } }); } else { return done(null, false, { message: 'Incorrect username.' }); } } )); //Passport 로그인이 성공되면, 그곳에서 입력한 데이터가 이곳에 콜백의 유저로 넘어 옵니다. passport.serializeUser(function(user, done) { console.log('serializeUser :' , user.id); done(null, user.id); }); //한번 통신하고 나면,웹이 리로드될때마다 이곳이 호출되어서 이곳에서 DB의 유저값을가져와 비교합니다. //심플한 테스트를 위해,db 에 있는값이 아닌 위의 값을 넣어줍니다. passport.deserializeUser(function(id, done) { var user = db.get('users').find({id:id}).value(); console.log('deserializeUser :',id,user); done(null, user); }); return passport; } | cs |
코드 공유
'노드JS [Express]' 카테고리의 다른 글
[passport 패스포트] 노드JS passport 정리글 (0) | 2020.07.01 |
---|---|
[세션 session] 노드JS Session 정리글 (0) | 2020.07.01 |
[쿠키 와 인증] 노드JS Cookie 인증 정리글 (2) | 2020.06.29 |
[Node JS Express and React 배우기] yarn dev 를 이용한 서버와 리액트 프론트 동시 실행방법. (0) | 2020.06.26 |
[노드 JS Express and React] yarn dev 를 이용한 서버와 리액트 프론트 동시실행 (0) | 2020.06.26 |