Authentication & Security

김주언·2022년 6월 7일

WEB - Basic

목록 보기
8/10

패스워드 보안

사용자 회원가입 시 이메일과 패스워드를 입력받아 DB에 저장할 때 패스워드를 plain text로 저장하지 않고 변형하여 저장한다.
사용자 로그인 시 입력받은 plain 패스워드와 DB에 변형되어 저장된 패스워드를 비교하여 로그인 결과를 반환한다.

프로젝트 구조

.
├── app.js
├── package.json
├── public
│   ├── css
│   └── favicon.ico
└── views
    ├── home.ejs
    ├── login.ejs
    ├── partials
    ├── register.ejs
    ├── secrets.ejs
    └── submit.ejs

기본 설정

login.ejs

<%- include('partials/header') %>
<!--생략-->
          <!-- Makes POST request to /login route -->
          <form action="/login" method="POST">
            <div class="form-group">
              <label for="email">Email</label>
              <input type="email" class="form-control" name="username">
            </div>
            <div class="form-group">
              <label for="password">Password</label>
              <input type="password" class="form-control" name="password">
            </div>
            <button type="submit" class="btn btn-dark">Login</button>
          </form>
<!--생략-->
<%- include('partials/footer') %>

app.js

require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const ejs = require('ejs');
const mongoose = require('mongoose');
const app = express();

app.use(express.static('public'));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({ extended: true }));

mongoose.connect('mongodb://localhost:27017/userDB', { useNewUrlParser: true });

const userSchema = new mongoose.Schema({
  email: String,
  password: String,
});

const User = mongoose.model('User', userSchema);

app.get('/', (req, res) => {
  res.render('home');
});
app.get('/login', (req, res) => {
  res.render('login');
});
app.get('/register', (req, res) => {
  res.render('register');
});

app.post('/register', (req, res) => {
    const newUser = new User({
      email: req.body.username,
      password: req.body.password
    });
    newUser.save((err) => {
      if (err) {
        console.log(err);
      } else {
        res.render('secrets');
      }
    });
});

app.post('/login', (req, res) => {
  const username = req.body.username;
  const password = req.body.password;

  User.findOne({ email: username }, function (err, foundUser) {
    if (err) {
      console.log(err);
    } else {
      if (foundUser.password == password) {
        res.render('secrets');
      }
    }
  });
});

app.listen(3000, () => {
  console.log('Server running on 3000');
});

위의 코드들은 DB에 사용자의 password를 plain text로 저장하여 보안성이 떨어진다. 따라서 패스워드를 저장할 때는 다양한 암호화를 수행한 후 저장하는 것이 바람직하다.


Hashing

bcrypt 패키지 설치

$ npm i bcrypt

app.js에 추가

const bcrypt = require('bcrypt');
const saltRounds = 10;

패스워드 hashing

bcrypt.hash(plaintextPassword, saltRounds, function(err, hash) {
    // DB에 패스워드 저장하는 코드
});
  • 매개변수로 플레인 패스워드, 솔트값, 콜백함수를 받는다.
  • 콜백 함수는 패스워드 암호화가 완료되면 실행되며 에러와 암호화된 패스워드(해시)를 매개변수로 받는다.
  • 자동으로 salt와 hash를 생성해준다.

app.js

app.post('/register', (req, res) => {

  bcrypt.hash(req.body.password, saltRounds, function (err, hash) {
    const newUser = new User({
      email: req.body.username,
      password: hash,
    });
    newUser.save((err) => {
      if (err) {
        console.log(err);
      } else {
        // 시크릿 페이지로 이동
        res.render('secrets');
      }
    });
  });
});

패스워드 check

// DB에서 패스워드 로드
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
    // result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
    // result == false
});

compare(data, encrypted, cb)

  • data : 플레인 패스워드
  • encrypted : data와 비교할 데이터. 암호화된 패스워드
  • cb : result는 data와 encrypted이 동일하면 true를 반환
app.post('/login', (req, res) => {
  const username = req.body.username;
  const password = req.body.password;
	
  // DB에서 email 속성이 username인 문서 검색
  // 검색결과를 콜백으로 확인한다.
  User.findOne({ email: username }, function (err, foundUser) {
    if (err) {
      console.log(err);
    } else {
      // 유저가 DB에 등록되어 있을 때
      if (foundUser) {
        // 입력받은 패스워드와 DB에 암호화되어 저장된 패스워드를 비교
        bcrypt.compare(password, foundUser.password, function (err, result) {
          if (result == true) {
            res.render('secrets');
          }
        });
      }
    }
  });
});
profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글