Session을 이용한 로그인 구현(bcrypt)


이번에는 session과 bcrypt를 이용해 로그인/아웃 기능을 구현할겁니다.

우선 필요한 모듈들을 아래 코드로 불러옵니다.

npm install express-session express-mysql-session bcrypt --save

express-session은 session 객체를 req에 추가해주는 모듈, express-mysql-session은 세션 데이터를 mysql 서버에 저장하기 위한 express-session에 의존하는 모듈, bcrypt는 비밀번호 해싱을 위한 모듈입니다.

app.js에서 세션을 이용하기 위해 아래 코드를 추가해줍니다.

세션 스토어를 설정해야 session 데이터를 sql 서버에 저장할 수 있습니다.

const session = require('express-session');
const MySQLStore = require('express-mysql-session')(session);

// create session store
let sessionStore = new MySQLStore({}, mysqlConnection);

app.use(session({
  key: 'LoginSession',
  secret: 'Secret',
  store: sessionStore,
  resave: false,
  saveUninitialized: true,
  cookie: {
    maxAge: 60*60*1000
  }
}))

우선 로그인 페이지를 만들어주기 위해 views폴더 아래에 login.pug 파일을 아래와 같이 만들어줍니다.

login.pug

extends top-layout

block content
  .Login
    form(action='login' method='post')
      .Login-input
        label(for='id') ID
        input(type='text' name='id')
        br
        label(for='id') password
        input(type='password' name='password')

      .Login-submit
        input(type='submit' value='로그인')

top-layout.pug 파일 body 바로 아래에 로그인 페이지로 가는 링크를 만들어줍니다.

    if(session)
      .Top
        .Top-right
          if(session.nickname !== undefined)
            a(href='/logout') 로그아웃
          else
            a(href='/login') 로그인

그리고 index.js 파일에 아래와 같이 get요청을 처리해줍니다.

router.get('/login', (req, res) => {
  res.render('login', {
    session: req.session
  });
})

이 사이트에선 어차피 저 혼자만 로그인 할 거기 때문에 ID와 password를 config.env에 저장해 놓을 겁니다.

그런데 패스워드는 평문으로 저장하면 보안이 너무 취약해지므로 bcrypt로 해싱을 한 후 저장할겁니다.

아래 코드를 돌려서 얻은 hash값과 ID를 config.env에 저장해줍니다.

const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 'my password';

bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
    console.log(hash)
});

index.js에서 bcrypt.compare 함수를 이용해 비밀번호를 체크하고 session에 nickname을 추가합니다(session.save 함수는 위에서 설정한 session store에 session을 저장합니다).

이후부터는 session에 nickname이 존재하는지 여부에 따라 로그인 여부를 확인할 수 있습니다.

router.post('/login', (req, res) => {
  if(req.body.id !== process.env.ID){
    res.render('error', {
      message: "존재하지 않는 ID입니다."
    })
    return;
  }

  bcrypt.compare(req.body.password, process.env.PASSWORD, (err, result) => {
    if(err) throw(err);
    if(result) {
      req.session.nickname = req.body.id;
      req.session.save((err) => {
        if(err) throw(err);
        res.redirect('/');
      })
    }
    else {
      res.render('error', {
        message: "password가 일치하지 않습니다."
      })
      return;
    }
  });
})

로그아웃은 간단하게 session에서 nickname을 제거하는걸로 처리합니다.

router.get('/logout', (req, res) => {
  delete req.session.nickname;
  req.session.save(()=> {
    res.redirect('/');
  })
})

아래는 결과입니다.

이미지

이미지

로그인과 로그아웃이 잘 작동하는 것을 확인할 수 있습니다.

다음에는 글 쓰기/읽기/목록 표시를 다루겠습니다.