POMOTODO : session, passport, serialize, deserialize

2022. 1. 5. 14:59프로그래밍/개인프로젝트

POMOTODO를 만들면서 작성한 코드를 복습하기 위해 작성하는 글입니다.

 

 

POMOTODO.kr

 

 

What is POMOTODO?

 


 

Node.js의 라이브러리인 Express에서 Session을 이용해 인증을 구현하도록 한다

 

쿠키의 등장으로 이전에 통신했던 내역을 기억할 수 있게 되었다

하지만 쿠키가 유출될 수 있고 조작될 수 있기때문에 인증을 구현하는것은 매우 위험하다

세션을 적용해서 인증하도록 해서 쿠키를 암호화 할 수 있다.

세션은 각각의 사용자를 식별하기 위한 식별자로서만 기능하는것이고

이것에 대한 실제 데이터는 서버에 저장해서  안전하게, 훨씬 더 많은 정보를 저장할 수 있다

직접 구현하는것이 가능하지만 복잡하고 어렵기 때문에 Express의 도움을 받아서 쉽게 구현한다.

-생활코딩-

 

1. 터미널을 켜고 라이브러리를 설치한다

 

npm install passport passport-local express-session

 

2. server.js 상단에 설치한 라이브러리를 첨부해준다

//server.js

const express = require('express');
const app = express();

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');

// app.use()는 미들웨어를 쓰겠다는 말임
// 미들웨어 : app.use = request - response 중간에 실행되는 코드
app.use(session({
    secret : 'secret code', 
    resave : true, 
    saveUninitialized: false,
    cookie: {
        httpOnly: true,
        secure: false
    }
})); 

app.use(passport.initialize());
app.use(passport.session());

 

3. login.ejs를 작성한다

 

input태그의 name을 사용할 것이므로 기억해둔다 (loginId, loginPassword)

 

login.ejs

<!--login.ejs-->
<!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">

    <link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png">
    <link rel="manifest" href="favicon/site.webmanifest">
    <link rel="mask-icon" href="favicon/safari-pinned-tab.svg" color="#5bbad5">
    <meta name="msapplication-TileColor" content="#da532c">
    <meta name="theme-color" content="#ffffff">
    
    <title>POMOTODO</title>
    <link rel="stylesheet" href="login-signup.css" />
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> 
    <script defer src="login.js"></script>
</head>
<body>
    <div class="container">
      <h1>log in</h1>
      <div class="login-title-underline"></div>
      <form method="post" name="loginForm" id="authForm" action="/loginPost">
          <div class="form-list">
            <input type="text" class="form-list" id="loginId" name="loginId" placeholder="username" minlength="5" maxlength="10" autocomplete='off' autofocus>
          </div>
          <div class="form-list">
            <input type="password" class="form-list" id="loginPw" name="loginPassword" placeholder="password" minlength="5" maxlength="20" autocomplete='off'onkeydown="if(window.event.keyCode==13){printName()}">
          </div>
          <div class="id-check"><%= loginFeedback %></div>
          <button type="button" class="form-list" id="form-login" onclick='printName()' >login</button>
          <button type="button" class="form-list" id="login-toHome" onclick="location.href='/'">home</button>
          <button type="button" class="form-list" id="login-toSignup" onclick="location.href='/signup'">sign up</button>
          <button type="button" class="form-list" id="form-pomotodo" onclick="window.open('https://github.com/coqoa/POMOTODO/blob/main/README.md')">what is POMOTODO?</button>
      </form>
    </div>
</body>
</html>

login.js

//login.js
function printName(){
    console.log('printName');
    var loginForm = document.loginForm;
    var loginId = loginForm.loginId.value;
    var password = loginForm.loginPassword.value;
    loginForm.submit();
}

 

4. server.js에서 '/loginPost'로 post요청을 받으면 어떻게 처리할지 작성해준다

 

미들웨어로 passport를 넣어줘서 요청과 응답사이에 인증을 하도록 한다

//server.js

app.post('/loginPost', passport.authenticate('local', { // 로컬방식으로 인증
    failureRedirect : '/fail' ,
    failureFlash : true
    //실패시 /fail페이지로 이동시켜주세요
    }), function(req, res){
        req.session.save(function(){
            // 성공시 redirect해서 홈페이지로 보내주기
            res.redirect('/') 
        })
});

5. server.js 하단에 어떤식으로 인증할 지 작성해준다.

//server.js

    //new LocalStrategy( { 설정 }, function(){ 아이디비번 검사하는 코드 } )
    passport.use(new LocalStrategy({
         usernameField: 'loginId', //사용자가 제출한 id가 어디 적혔는지(input의 name 속성값)
         passwordField: 'loginPassword', //사용자가 제출한 pw가 어디 적혔는지(input의 name 속성값)
         session: true,  //로그인 후 세션을 저장할지 ?
         passReqToCallback: false, //사용자가 입력한 id,pw외에 다른정보도 검증해보고 싶으면

    }, function (inputId, inputPw, done) {
         // console.log(inputId, inputPw) -> 사용자가 입력한 id/pw가 콘솔로그로 출력됨
        db.collection('users').findOne({ id: inputId }, function (err, user) {
             //입력한 id에 대한 정보를 결과에 담아옴
             if (err) return done(err) //에러처리문법
            if (!user) { //db에 없는 아이디일때?
                return done(null, false, { message: 'ID does not exist' })
                 //done은 3개의 파라미터를 가질수 있음, 1: 서버에러, 2: 성공시 사용자db, 3:에러메시지
            }
            if(user){ // db에 아이디가 존재하면?
                hasher({password:inputPw, salt: user.saltPassword}, function(err, pass, salt, hash){
                     // console.log(err, pass, salt, hash);
                     // err = undefined, pass:입력한비밀번호값, salt: 랜덤번호생성, hash: 입력비밀번호+salt값에 대한 해쉬값 을 출력해준다
                     // 입력비밀번호와 유저의 salt를 가져와서 hash로 만들고 그 해쉬값이 서버의 해쉬값과 일치하다면  로그인성공
                    if (hash == user.hashPassword) { // 인증완료 - 로그인
                        return done(null, user)
                    } else { // 비밀번호가 틀렸을 때
                        return done(null, false, { message: 'password incorrect' })
                    }
                })
            }
        })
    }));

* 적용되어있는 암호화는 따로 포스팅할예정

 

6. 세션을 만들고 세션아이디 발급해서 쿠키로 보내준다

     //user 파라미터로 아이디/비밀번호 검증 결과가 들어간다
    passport.serializeUser(function (user, done) {
         done(null, user.id); // 세션데이터안에 passport의 user값으로 사용자의 아이디가 들어간다
    });
     // 세션데이터를 가진 사람을 db에서 찾아주는 코드
     passport.deserializeUser(function(아이디, done) { //로그인하면 페이지에 방문할 때 마다 콜백함수가 호출, 사용자의 실제 데이터를 조회해서 가져옴
        db.collection('users').findOne({ id: 아이디 }, function (err, result) {
            done(null, result);
        })
    });

 

7. session이 있는사람과 없는사람을 구분해서 홈페이지를 호출해주기 위해 미들웨어를 만들고 적용한다

    function homeLoginCheck(req, res, next) { 
        if (req.user) { //세션이 있으면 통과
            next(); 
        } else { //세션이 없으면 이런 페이지 생성
            res.render('POMOTODO.ejs', { posts : 'log in', pomodoroRecord : ' ', todoListRecord : ' ', notTodoListRecord : ' ' });
        } 
    } 
    
    app.get('/',homeLoginCheck,function(req, res){
        // console.log('----get-req----');
        // console.log(req.user.id);
        // console.log('----get-req----');
        // pomodoro 기록 출력하는 코드
        db.collection('pomodoro').findOne({id:req.user.id}, function(err, pomodoroResult){
            pomoResult = pomodoroResult.contentHTML;

            db.collection('todolist').findOne({id : req.user.id}, function(err, todolistResult){
                todoResult = todolistResult.todoListHTML;

                db.collection('not-todolist').findOne({id : req.user.id}, function(err, nottodolistResult){
                    notTodoResult = nottodolistResult.notTodoListHTML;

                    //record컬렉션을 조회해서 없으면 만들어주는 코드
                    db.collection('pomodoro-record').findOne({ id: req.user.id, 'yyyymmdd' : yyyymmdd() }, function (err, pomoRecCheck) {
                        if(pomoRecCheck==null){
                            db.collection('pomodoro-record').insertOne({ 'id' : req.user.id, 'yyyymmdd' : yyyymmdd() ,'pomoRecord' : '' }, function(err, result){
                                console.log('db pomodoro-record create')
                            })
                        }
                    })
                    db.collection('todolist-record').findOne({ id: req.user.id, 'yyyymmdd' : yyyymmdd() }, function (err, todoRecCheck) {
                        if(todoRecCheck==null){
                            db.collection('todolist-record').insertOne({ 'id' : req.user.id, 'yyyymmdd' : yyyymmdd() ,'todoRecord' : '' }, function(err, result){
                                console.log('db todo-record create')
                            })
                        }
                    })
                    db.collection('not-todolist-record').findOne({ id: req.user.id, 'yyyymmdd' : yyyymmdd() }, function (err, notTodoRecCheck) {
                        if(notTodoRecCheck==null){
                            db.collection('not-todolist-record').insertOne({ 'id' : req.user.id, 'yyyymmdd' : yyyymmdd() ,'notTodoRecord' : '' }, function(err, result){
                                console.log('db not-todo-record create')
                            })
                        }
                    })
                    res.render('POMOTODO.ejs', { posts : req.user.id, pomodoroRecord : pomoResult, todoListRecord : todoResult, notTodoListRecord : notTodoResult}); 
                })
            })
        })
    });