Node.js 기반 홈페이지 만들기 (2) - Crypto, Passport

 

시리즈

기능 동작이 목표기 때문에 자세한 설명은 하지 않습니다.

  • IDE : WebStorm
  • DB : MongoDB
  • OS : MAC

HASH

Node.js 기반 홈페이지 만들기 (1)을 참고해 회원가입 기능을 구현했다면, 해킹을 당해 유출이 된다면 어떠한 결과가 초래될지 생각해봐야 합니다.

물론 해킹을 방지하려면 시큐어코딩을 적용해야합니다. 하지만 당장 공부하고, 코딩하기에 무리가 있으므로 최소한 유출이 되더라도 피해를 줄이기 위한 방법을 사용해야합니다. 그 방법은 바로 비밀번호를 해시함수를 이용해 암호화하여 데이터베이스에 저장하는 것입니다.

  • 복호화를 방지하기 위해 salt를 사용해야되지만, 더 많은 설명이 필요해지므로 단순한 방법 및 간단한 코드로 실습합니다.

Crypto를 사용하기 위해 npm으로 모듈을 설치합니다.

npm install crypto --save

Crypto는 Node.js에서 제공하는 다양한 암호화기능을 수행하는 모듈입니다.
다양한 사용법과 설명은 공식 docs에서 볼 수 있습니다. 지금은 단방향 알고리즘을 가장 기본적인 문법으로 사용합니다.

  • 단방향 알고리즘은 평문을 암호화하였을 때, 암호문을 다시 평문으로(복호화) 할 수 없습니다.
  • 양방향 알고리즘은 복호화 할 수 있습니다.

crypto 모듈을 부른 뒤, createHash 메소드는 sha512로 정하고, update에 암호화할 password를 넣고, base64로 인코딩하는 코드입니다.

crypto.createHash('sha512').update(password).digest('base64')

POST로 받은 form의 값들을 DB에 저장하는 코드에서 password를 req.body.password로 전달받은 그대로가 아닌 crypto 모듈을 사용해 암호하도록 코드를 변경합니다.

routes/index.js

const crypto = requrie("crypto");

const user = new User({
                    _id: new mongoose.Types.ObjectId(),
                    name:req.body.name,
                    email: req.body.email,
                    password: crypto.createHash('sha512').update(req.body.password).digest('base64')
                });

회원가입을 해보면, 비밀번호가 암호화되어 저장되는 것을 볼 수 있습니다.

7

LOGIN

로그인을 위한 폼을 만듭니다. 로그인 form의 action은 /login 그리고 method는 post로 합니다.

views/login.ejs

<div style="text-align: center; color:red; margin-top: 2%">
<!--만약 message의 길이가 0보다 길면 message를 띄워라-->
    <% if(message.length > 0) { %>
    <%= message %>
    <% } %>
    </div>
    <form action="/login" method="POST" class="signupform">
        <label for="email">E-MAIL</label>
        <input name="email" type="email" required />
        <label for="password">PASSWORD</label>
        <input name="password" type="password" required />
        <button class="btn">LOGIN!</button>
    </form>

로그인 시 로그인 한 회원의 이메일을 메인페이지에 띄우기 위해 코드를 수정합니다.

  • <% %> : 자바스크립트 코드
  • <%= %> : 변수(값)

views/index.ejs

<span class="title">Hello! <%=email%></span>

Node.js의 인증 미들웨어인 Passport.js를 사용해 로그인 기능을 구현합니다. 관련 모듈들을 npm으로 설치합니다.

  • passport : 인증 코어 모듈
  • passport-local : (소셜 네트워크 로그인 등을 제외한) 로컬 로그인 모듈
  • express-session : Express 자체 내장된 세션 기능을 따로 모듈화 시킨 모듈
  • connect-mongodb-session : 몽고DB에 세션을 저장하기 위한 모듈
  • connect-flash : 플래시 메시지를 위한 모듈
npm install passport --save //인증
npm install passport-local --save //로컬로그인
npm install express-session --save //세션
npm install connect-mongodb-session --save //세션을 데이터베이스에 저장
npm install connect-flash --save //플래시 메시지

먼저, npm으로 설치한 모듈들을 require합니다.

app.js

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const Session = require('express-session');
const flash = require('connect-flash');
var MongoDBStore = require('connect-mongodb-session')(Session);

세션설정과 미들웨어 설정을 하는 코드를 추가합니다. 옵션값에 대한 정보는 주석으로 표시해두었으니 참고바라며 더 자세히 알고 싶다면 각 모듈의 공식 docs를 읽어보길 추천합니다.

app.js

app.use(flash());

//세션
var store = new MongoDBStore({//세션을 저장할 공간
    uri: url,//db url
    collection: 'sessions'//콜렉션 이름
});

store.on('error', function(error) {//에러처리
    console.log(error);
});

app.use(Session({
    secret:'dalhav', //세션 암호화 key
    resave:false,//세션 재저장 여부
    saveUninitialized:true,
    rolling:true,//로그인 상태에서 페이지 이동 시마다 세션값 변경 여부
    cookie:{maxAge:1000*60*60},//유효시간
    store: store
}));
app.use(passport.initialize());
app.use(passport.session());

패스포트를 require 하고, 로그인에 성공할 시 정보를 세션에 저장하는 코드와 인증 후에 페이지 이동등의 요청이 있을 때마다 호출하는 코드를 추가합니다.

routes/index.js

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

//로그인에 성공할 시 serializeUser 메서드를 통해서 사용자 정보를 세션에 저장
passport.serializeUser(function (user, done) {
    done(null, user);
});

//사용자 인증 후 요청이 있을 때마다 호출
passport.deserializeUser(function (user, done) {
    done(null, user);
});

로그인 로직 코드입니다. 주의할 점은 password를 암호화하여 저장하였으므로,
로그인 코드를 작성할 때도 사용자가 입력한 패스워드를 암호화한 값과 DB에 저장된 패스워드와 비교하여야 합니다.

routes/index.js

passport.use(new LocalStrategy({
        usernameField: 'email',
        passwordField : 'password',
        passReqToCallback : true//request callback 여부
    },
    function (req, email, password, done)
    {
        User.findOne({email: email, password: crypto.createHash('sha512').update(password).digest('base64')}, function(err, user){
            if (err) {
                throw err;
            } else if (!user) {
                return done(null, false, req.flash('login_message','이메일 또는 비밀번호를 확인하세요.')); // 로그인 실패
            } else {
                return done(null, user); // 로그인 성공
            }
        });
    }
));

router.post('/login', passport.authenticate('local', {failureRedirect: '/login', failureFlash: true}), // 인증 실패 시 '/login'으로 이동
    function (req, res) {
        res.redirect('/');
        //로그인 성공 시 '/'으로 이동
    });

처음에 login.ejs를 작성할 때, message를 띄우는 코드를 작성했습니다. 또한, 로그인 로직 코드에서 req.flash로 플래시메시지를 전달하도록 하였지만 작동하지 않을 것입니다.
GET방식으로 /login으로 접속할 때 message를 전달하지 않았기 때문입니다.

routes/index.js

router.get("/login", (req, res) => res.render("login", {message: req.flash('login_message')}));

등록되어있지 않은 이메일 또는 틀린 비밀번호로 로그인 시도 시 지정한 실패 문구가 뜨는 것을 볼 수 있습니다. 8 9

LOGOUT

로그아웃을 하기 위해 간단한 버튼을 추가합니다.

vies/index.ejs

<input type="button" class="btn" onclick=" location.href='/logout'" value="LOGOUT"/>

로그아웃 코드는 매우 간단합니다.

routes/index.js

router.get('/logout', function (req, res) {
    req.logout();
    res.redirect('/'); //로그아웃 후 '/'로 이동
});

로그인을 한 상태에서 로그아웃을 하면 세션이 사라지는 것을 볼 수 있습니다.

10 11 12

앞으로 업로드 할 글 예정

  • 3번째 글 : 게시글 올리기, 게시글 보기, 게시글 수정
  • 4번째 글 : 이메일 인증, 내정보 수정
  • 5번째 글 : 관리자

https://github.com/dalhav/simple-nodejs-page/tree/two 에서 이 글에서 사용한 코드를 볼 수 있습니다.