로그인 - passport package

OneTwoThree·2022년 11월 14일

nodejs

목록 보기
28/33

참고링크

  • passport는 nodejs에서 사용자 인증 (login) 을 만들기 위해 널리 사용되는 패키지

  • 단독으로 사용할 수 없고 passport strategy package와 함께 사용해야함

  • passport package : 인증 시스템을 위한 base

  • passport strategy package : 구체적인 인증 방법을 구현

이렇게 구성되는 이유는 사이트에 필요한 패키지만 설치하기 위해서임


로그인 원리

  • client-server간의 정보교환은 단발성 : request, response
  • client를 구별하기 위해 각각의 request에 식별코드 필요
  • 로그인을 성공하게 되면 server의 session에 기록, 다음번 request 부터는 기록된 상태 (로그인 상태) 유지
  • 로그인은 DB에 등록된 유저를 찾는 것
  • DB에서 유저를 찾아서 session에 그 정보 (전부 혹은 일부)를 기록하는 것 : serialize
  • session에 등록된 user 정보를 이용해서 user 객체를 만드는 것 : deserialize : 서버에 요청이 올 때 마다 deserialize 하게됨

폴더구조

패키지 설치

passport 패키지, passport-local 패키지 설치

코드

// index.js

...
var session = require('express-session');
var passport = require('./config/passport'); //1
var app = express();

...

// Passport // 2
app.use(passport.initialize()); //passport 초기화 
app.use(passport.session()); //passport와 session 연결 

// Custom Middlewares // 3
app.use(function(req,res,next){ 
//req.isAutehnticated는 현재 로그인이 되어 있는지 아닌지를 true나 false로 반환함 (passport에서 제공하는 함수)
  res.locals.isAuthenticated = req.isAuthenticated();
  //req.user는 로그인이 되면 session으로 부터 유저 정보를 deserialize 해서 생성함 
  res.locals.currentUser = req.user;
  
  // res.locals.isAuthenticated에 로그인 했는지 안했는지 여부를 저장
  // res.locals.currentUser로 로그인된 유저의 정보를 불러옴 
  next();
});

...

index.js (Coogle의 app.js)의 코드다.

  • 설치한 모듈을 바로 require 하지 않고 config 디렉토리의 passport.js를 담아옴
    설치한 모듈은 passport.js에 들어있음

  • session은 express-session 패키지로부터 생성되므로 로그인을 구현하기 위해서는 session생성 코드 app.use(session({secret:'MySecret', resave:true, saveUninitialized:true}));가 반드시 필요함

  • app.use에 함수를 넣는 것을 미들웨어라고함
    app.use에 넣은 함수는 request url과 상관없이 request가 들어올 때마다 실행됨
    그런데 위에 있는 것부터 실행되기 때문에 순서가 중요함
    위의 경우 반드시 route보다 먼저 와야함


// config/passport.js

//그냥 이렇게 해 
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy; // 1
var User = require('../models/User');


// serialize & deserialize User // 2
//serializeUser는 로그인 할 때 유저의 정보를 세션에 어떻게 저장할 지 정한다. id만 저장하게 함 
passport.serializeUser(function(user, done) {
  done(null, user.id);
});
//deserializeUser는 session에서 정보를 가져와서 유저를 만들 때 어떻게 만들지 정함
// id를 이용해서 가져옴 
passport.deserializeUser(function(id, done) {
  User.findOne({_id:id}, function(err, user) {
    done(err, user);
  });
});

// local strategy // 3
passport.use('local-login',
  new LocalStrategy({
  //이 부분은 로그인 폼에서 입력받을 때 name 속성값을 가져와서 usernameField와 passwordField로 사용할 수 있게 해준다
  예를 들어 ejs 파일의 form에서 name="email"로 사용자가 id를 입력하면 usernameField : "email"로 해주면 된다. 
      usernameField : 'username', // 3-1
      passwordField : 'password', // 3-1
      passReqToCallback : true
    },
    //로그인 시에 호출되는 함수 
    function(req, username, password, done) { // 3-2
    //db에서 유저 찾고 입력받은 password와 일치하면 done에 담아서 리턴 
      User.findOne({username:username})
        .select({password:1})
        .exec(function(err, user) {
          if (err) return done(err);

//authenticate는 입력받은 password와 db에서 읽어온 해당 user의 password hash를 비교하는 함수
          if (user && user.authenticate(password)){ // 3-3
            return done(null, user);
            //done의 첫번째 파라미터는 에러를 담기 위한 것
            //에러가 없으면 null을 담는다 
          }
          else { //일치하지 않을경우 flash 에러 
            req.flash('username', username);
            req.flash('errors', {login:'The username or password is incorrect.'});
            return done(null, false);
        
          }
        });
    }
  )
);

module.exports = passport;

// routes/home.js

var express = require('express');
var router = express.Router();
//상대주소라 점 2개 ../ 로 시작 
var passport = require('../config/passport'); // 1

// Home ...

// Login // 2
//로그인 뷰를 보여줌 
router.get('/login', function (req,res) {
  var username = req.flash('username')[0];
  var errors = req.flash('errors')[0] || {};
  res.render('home/login', {
    username:username,
    errors:errors
  });
});

// Post Login // 3
// 로그인 폼에서 입력 후 넘어오는 요청 처리, 콜백함수2개  
router.post('/login',
  function(req,res,next){
    var errors = {};
    var isValid = true;

    if(!req.body.username){
      isValid = false;
      errors.username = 'Username is required!';
    }
    if(!req.body.password){
      isValid = false;
      errors.password = 'Password is required!';
    }

    if(isValid){
      next();
    }
    else {
    //유효하지 않은 입력일 경우 flash로 에러 발생시키고 로그인폼으로 리다이렉트 
      req.flash('errors',errors);
      res.redirect('/login');
    }
  },
  //passport의 local strategy를 호출해서 로그인 진행 
  passport.authenticate('local-login', {
    successRedirect : '/posts',
    failureRedirect : '/login'
  }
));

// Logout // 4
//로그아웃 하고 리다이렉트 
router.get('/logout', function(req, res) {
  req.logout();
  res.redirect('/');
});

module.exports = router;

참고 : flash

참고링크

npm install express-flash
import express from "express";
import flash from "express-flash";

const app = express();

/ 세션 코드 .... /
app.use(flash());


flash를 호출하면 템플릿에 1회성 메세지를 전달가능

 req.flash("authorized", "Your Permission Denied.");

이렇게보내면 템플릿에서 authorized로 접근해서 Your permission denied를 쓸 수 있다.
일회성 메세지라 한번 보여주고 나면 세선에서 사라진다.


<!-- views/home/login.ejs -->

<!DOCTYPE html>
<html>
  <head>
    <%- include('../partials/head') %>
  </head>
  <body>
    <%- include('../partials/nav') %>

    <div class="container">

      <h3 class="mb-3">Login</h3>

      <form class="user-form" action="/login" method="post">

        <div class="form-group row">
          <label for="username" class="col-sm-3 col-form-label">Username</label>
          <div class="col-sm-9">
            <input type="text" id="username" name="username" value="<%= username %>" class="form-control <%= (errors.username)?'is-invalid':'' %>">
            <% if(errors.username){ %>
              <span class="invalid-feedback"><%= errors.username %></span>
            <% } %>
          </div>
        </div>

        <div class="form-group row">
          <label for="password" class="col-sm-3 col-form-label">Password</label>
          <div class="col-sm-9">
            <input type="password" id="password" name="password" value="" class="form-control <%= (errors.password)?'is-invalid':'' %>">
            <% if(errors.password){ %>
              <span class="invalid-feedback"><%= errors.password %></span>
            <% } %>
          </div>
        </div>

        <% if(errors.login){ %>
          <div class="invalid-feedback d-block"><%= errors.login %></div>
        <% } %>

        <div class="mt-3">
          <input class="btn btn-primary" type="submit" value="Submit">
        </div>

      </form>

    </div>
  </body>
</html>

로그인을 위한 뷰


요약


접근제한

로그인 여부에 따라 접근제한

// util.js

var util = {};

util.parseError = function(errors){
 ...
}

//사용자가 로그인되어있는지 확인해서 로그인 안했을 경우 에러메세지와 함께 로그인페이지로 보낸다 
util.isLoggedin = function(req, res, next){
  if(req.isAuthenticated()){
    next();
  } 
  else {
    req.flash('errors', {login:'Please login first'});
    res.redirect('/login');
  }
}

//접근권한이 없다고 판단된 경우에 로그인페이지로 보낸다
//req.isAuthenticated로 접근권한을 확인하지 않는 경우(다른 방법으로 판단하는경우)
util.noPermission = function(req, res){
  req.flash('errors', {login:"You don't have permission"});
  req.logout();
  res.redirect('/login');
}

module.exports = util;

0개의 댓글