nodejs

인프런 node.js 웹개발로 알아보는 백엔드

동영상 강좌를 통해 node.js를 학습하며 정리하는 포스팅입니다.

1. 세션 유지 및 로그인

로그인을 했을때 정보를 서버상의 상태값을 지속적으로 유지함으로 해당 사용자가 접근시 로그인한 상태의 정보와 로그인이 되지 않았을때 정보에 대해 구분하기 위한 기능을 말합니다. 최근은 세션의 상태값이 아닌 토큰을 활용한 세션인증을 많이 활용하기도 합니다.

인증관련 기능은 passport를 활용하여 구현할 수 있습니다. 터미널에서 다음과 같은 명령어를 입력합니다.

npm install passport passport-local express-session connect-flash --save-dev

위의 명령어의 의미는 세션처리 및 로컬을 활용, flash는 에러 메세지를 리다이렉트 과정에서 쉽게 전달할 수 있는 역할을 합니다.

app.js

아래의 코드를 추가합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var passport = require('passport')
var localStrategy = require('passport-local').Strategy
var session = require('express-session')
var flash = require('connect-flase')

app.use(session({
  //세션 암호화 키값
  secret : 'keyboard cat',
  //세션이 있는데 재요청시 true, false
  //default 요소로 2개는 반드시 명시
  resave : false,
  saveUninitialized : true
}))

app.use(passport.initialize())
app.use(passport.sessin())
app.use(flash())

app.use(router)

join.js(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
var express = require('express')
var router = express.Router()
var passport = require('passport')
var LocalStrategy = require('passport-local').Strategy

router.get('/', function(req,res) {
   var msg;
    var errMsg = req.flash('error')
    if(errMsg) msg = errMsg;
    res.render('join.ejs', {'message' : msg});
})

router.post('/', passport.authenticate('local-join', {
    successRedirect : '/main', //인증성공시 이동하는 화면주소
    failureRedirect : '/join', //인증실패시 이동 주소
    failureFlash : true })
)

//passport에서 done에서 값이 있을때 처리하는 기능(세션id 저장)
passport.serializeUser(function(user, done) {
    console.log('passport session save id : ', user.id);
    done(null, user.id);
})

//세션id를 각 필요한 페이지에 사용
passport.deserializeUser(function(id, done) {
    console.log('passport session get id: ', id);
    done(null, id);
})

passport.use('local-join', new localStrategy({
    usernameField : 'email',
    passwordField : 'password',
    passReqToCallback : true },
    function (req, email, password, done) {
    var query = connection.query('select * from USER where email=?', [email], function(err, rows) {
        if(err) return done(err);

        if(rows.length) {
            console.log("existed user");
            return done(null, false, {message : 'your email is already used'})
        } else {
          //해당 유저가 없을 시 정보 추가
            var sql = {email : email, pw:password};
            var query = connection.query('insert into USER set ?', sql, (err, rows)=>{
                if(err) throw err;
                return done(null, {'email' : email, 'id' : rows.insertId })
            })
        }
    })
   }
));

passport는 인증관련 모듈을 처리하며 passport-local(일반 로그인), session, flash(리다이렉트시 에러메시지 전송) 등 다양한 기능을 함께 사용할 수 있습니다. 사전에 적용한 router를 기반으로 URL 요청을 받아 passport.authenticate 함수를 통해 처리하도록 합니다. 해당 함수는 첫번째 인자로 passport의 선택자를 가리키며 이는 passport.use라는 함수로 받아 인증처리를 실행하는 LocalStrategy에 파라미터를 받아 해당 요청에 따른 작업 후 응답을 보내는 처리를 진행합니다.

rows.length 의 결과값이 존재할 때는 메세지를 done함수에 인자로 넣어 실패 페이지 즉 failureRedirect로 응답하며 있을 때는 파라미터로 보여주고자 하는 해당 옵션 값을 함께 보내주며 응답합니다. main.js에서는 get으로 해당 요청을 받아 res.render로 인자와 함께 view페이지에서 데이터를 표시할 수 있습니다.(<%= id %>)

main.js

1
2
3
4
5
6
7
8
router.get('/', function(req, res) {
  //세션 인증 성공시 id와 응답처리(세션 id 값)
    console.log("main.js -> ", req.user)
    var id = req.user;

    res.render('main.ejs', {'id': id})
    // res.sendFile(path.join(__dirname, "../public/main.html"))
});

2. ajax 로그인

구성 페이지

  • login.js
  • login.ejs

login.ejs

ajax로 처리하기 위한 함수를 구현합니다. sendAjax함수는 url과 데이터 객체를 받아 json형식으로 요청 및 응답을 처리하도록 구성하였습니다.

객체 new XMLHttpRequest()를 활용해서 method와 content-type, json등을 설정해주고 요청에 따른 결과값은 이벤트리스너 함수를 통해 JSON.parse를 이용하여 응답처리를 할 수 있도록 하였습니다.

 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>form</title>
</head>
<body>
    <form action="/email/form" method="post">
        email : <input type="text" name="email"><br>
        password : <input type="password" name="password"><br>
    </form>
    <button class="ajaxSend">Login</button>

    <div class="result"></div>

    <script>
        document.querySelector('.ajaxSend').addEventListener('click', function() {
            var email = document.getElementsByName('email')[0].value;
            var password = document.getElementsByName('password')[0].value;
            sendAjax('http://localhost:3000/login', {'email' : email, 'password': password});
        })

        function sendAjax(url, data) {
            data = JSON.stringify(data);
            //URL로 보내기(method, url, data)
            var xhr = new XMLHttpRequest();
            xhr.open('POST', url);
            xhr.setRequestHeader('Content-Type',"application/json");
            xhr.send(data);
            
            xhr.addEventListener('load', function() {
                var result = JSON.parse(xhr.responseText);
                var resultDiv = document.querySelector('.result');

                if(result.email) {resultDiv.innerHTML = '<h1>ID : ' + result.email + '님 환영합니다.'}
                else resultDiv.innerHTML = result;
            });
        }
    </script>
</body>
</html>

login.js

login.js는 기존에 먼저 진행한 join.js와 비슷하고 다른 처리를 하는 부분은 다음과 같습니다.

위에 ejs에서 ajax로 전송한 요청을 아래 router.post가 받아 해당 요청에 대한 응답을 처리하는 로직입니다. user라는 인자는 클라이언트에서 전송한 email과 password가 담겨 있으며 해당 데이터가 있을때 login 처리를 하도록 구성합니다.

 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
router.get('/', function(req,res) {
    console.log("/login get")
    var msg
    //flash로 에러메세지를 조금 더 편하게 처리할 수 있다.
    var errMsg = req.flash('error')
    if(errMsg) msg = errMsg;

    res.render('login.ejs', {'message' : msg});
})

passport.use('local-login', new LocalStrategy({
    usernameField:'email',
    passwordField:'password',
    passReqToCallback : true
}, function (req, email, password, done) {
    var query = connection.query('select * from USER where email=?', [email], function(err, rows) {
        if(err) return done(err);
        //로그인했을때 조회 정보 있는 경우 (로그인 가능)
        if(rows.length) {
            return done(null, {'email' : email, 'id' : rows[0].uid })
        } else {
            //오류나 값이 없을때 done에 false 후 메세지를 작성
            return done(null, false, {'message' : 'your Login email info is not found :)'})
        }
     })
   }
));

router.post('/', function(req, res, next) {
    passport.authenticate('local-login', function(err, user, info) {
        if(err) res.status(500).json(err);
      //유저가 없을때 401 에러 응답 및 메세지
        if(!user) return res.status(401).json(info.message);  
			
        req.logIn(user, function(err) {
            if(err) {return next(err);}
            return res.json(user);
        });
    })(req, res, next);
});


3. 로그아웃

  • index.js (routing 기능)
  • logout.js

index.js

1
2
3
//로그아웃 url 처리 및 라우터 추가
var logout = require('./logout/logout')
router.use('/logout', logout)

logout.js

1
2
3
4
5
6
7
8
9
var express = require('express')
var router = express.Router()
// /logout의 요청이 들어왔을때 req.logout을 통해 세션연결 중지 후 리다이렉트
router.get('/', function(req, res) {
    req.logout();
    res.redirect('/');
});

module.exports = router;

req.logout() 을 통해 유지되고 있는 세션연결을 종료하며 이후 응답으로 / 루트 페이지로 리다이렉트해주는 방식으로 로그아웃을 구현하였습니다.

느낀점

강좌를 매번 보면서 느끼는 것은 눈으로 볼때와 실제로 코드를 작성할때 알고 있어도 큰 차이를 보인다는 점입니다. 이제는 어느정도 강좌를 눈으로 보는 속도는 빨라졌지만 실제로 그것이 실력 향상에 큰 효율성을 보이지는 않는 다는 점은 앞으로 개선해나가야 할 부분이라 생각이 들었습니다. 점차 학습하는 방법도 조금씩 요령이 생기고 앞으로 뭘 더 배우고 나아가야할지 시야도 넓어지는 경험을 할 수 있었습니다. 업무로 인해 시작했던 학습이지만 그보다 더 값진 경험과 배움을 얻어 가는 좋은 기회였습니다.