[부트캠프] 백엔드 - nodeJS 로그인 관리 모듈 (Passport)

RedPanda·2022년 8월 30일
0

NodeJS

목록 보기
9/11

npm으로 다운받은 cookie-parser와 express-session으로 열심히 쿠키와 세션을 만들었다.
그것들로 로그인 구현을 하려니 신경을 쓸 것이 이만저만 아니었다.

session의 만료 기한과 로그아웃을 했을 때만 고려해보아도 추가로 구현할 부분이 많았다.

그래서 이번 강의때 알아본 'Passport' 모듈에 대해 알아보고자 한다.

passport란?

Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application.

passport 공식 사이트에 나와있는 설명이다. nodeJS용 인증 미들웨어이며, facebook, twitter 등의 사용자 인증을 지원한다고 한다.

여기서는 지원하는 인증 방법을 Strategy라고 설명한다. 페이스북, 카카오, 구글 등 다양한 인증 방법이 존재하니 참고하여 사용하기 바란다.
https://www.passportjs.org/packages/

Passport-local 전략 구성

local로 사용하는 것이기 때문에 DB를 연동하는 것이 필수이다. 로그인 인증할 때 DB에서 데이터를 SELECT 해오기 때문이다.

sequelize 모듈을 다루는 글은 앞전에 포스팅해 두었다.

npm i passport passport-local bcrypt // bcrypt는 hash 암호화 모듈이다. DB에 비밀번호가 그대로 노출되는 것을 방지하자.

우선 passport 디렉토리를 만들고 index.js파일과 localStrategy파일을 만들어보자.

// 공식 문서의 원형 코드
passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      if (!user.verifyPassword(password)) { return done(null, false); }
      return done(null, user);
    });
  }
));

// passport/localStrategy.js
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;  // 다운받은 모듈에서 가져옴
const bcrypt = require("bcrypt");
// 비밀번호를 hash 암호화 해주는 모듈
const User = require("../models/user");
// 사용자의 DB를 불러옴 -> 로그인 시에 비교할 DB를 가져오는 것
module.exports = () => {
  passport.use(
    new LocalStrategy(
      {
        usernameField: "id", // 요청받은 id
        passwordField: "password", // 요청받은 password
      },
      async (id, password, done) => {
        try {
          const exUser = await User.findOne({ where: { userId: id } }); // 사용자의 아이디가 userId로 저장되어 있으므로 이렇게 비교한다.
          if (exUser) { // 사용자를 확인했을 때의 코드
            const result = await bcrypt.compare(password, exUser.password); 
            // bcrypt로 DB에 저장한 비밀번호를 복호화 (이후에 비밀번호 저장시 암호화할 것임.)
            if (result) {
              done(null, exUser);
            } else {
              done(null, false, { message: "비밀번호가 일치하지 않습니다." });
            }
          } else {
            done(null, false, { message: "가입되지 않은 회원입니다." });
          }
        } catch (error) {
          console.error(error);
          done(error);
        }
      }
    )
  );
};

코드의 원형과 크게 다른 점 없이 구현되었다. 이 부분이 local의 DB와 비교하는 코드일 것이다.
비밀번호가 추후에 암호화될 것이기 때문에 bcrypt로 복호화 작업을 해준다.

다음 코드는 세션에 대한 코드이다.
passport에서 express-session을 사용하여 세션을 관리해주기 때문에 요청 시의 세션을 확인하는 코드를 구현해준 것 같다.

이 코드들 또한 공식 문서에 잘 나와있다.

// 공식 문서
passport.serializeUser(function(user, cb) {
  process.nextTick(function() {
    cb(null, { id: user.id, username: user.username });
  }); // 요청할 콜백함수
});

passport.deserializeUser(function(user, cb) {
  process.nextTick(function() {
    return cb(null, user);
  }); // 요청할 콜백함수
});

// passport/index.js
const passport = require("passport");
const local = require("./localStrategy");
const User = require("../models/user"); // 로그인할 user를 담아둔 DB를 불러온다.

module.exports = () => {
  passport.serializeUser((user, done) => { // 초기 로그인 시에 실행
    done(null, user.id); // err시 null, 성공시 id를 리턴하여 deserializeUser에 id를 보냄
  });

  passport.deserializeUser((id, done) => { // 매 요청 시에 실행
    User.findOne({ where: { id } }) // DB에서 해당 user를 찾음
      .then((user) => done(null, user)) // user를 리턴
      .catch((err) => done(err));
  });

  local();
};

찾아보니 done(err, user.id) 메소드는 passport에서 사용하는 함수인 것 같다.
간단하게 다음에 실행하는 메소드에 값을 넘겨준다고 생각하면 된다.

모듈 연결과 초기 세팅 구현

로그인 전략을 미리 설명한 이유는 실행 파일에서 불러올 것이기 때문이다.

다음은 app.js의 코드들이다.
passport로 초기에 세션을 다뤄준 모습이다.

const session = require("express-session");
const passport = require("passport");
const passportConfig = require("./passport");
// passport 폴더에서 설정한 로그인 전략을 받아옴(index.js)
passportConfig(); // 패스포트 설정
app.use( // express-session으로 세션 정의
  session({
    resave: false,
    saveUninitialized: false,
    secret: process.env.COOKIE_SECRET,
    cookie: {
      httpOnly: true,
      secure: false,
    },
    name: "session-cookie",
  })
);

app.use(passport.initialize());
app.use(passport.session());
// passport를 초기화하고 세션을 다룰 수 있게 해줌

로그인을 확인하는 미들웨어 라우터

각 미들웨어, rest API의 메소드들을 사용할 때 인증과정을 거치도록 미들웨어를 생성해주었다.

ex. app.get("/chatRoom", isLoggedIn, (req, res) =>{...});

// routes/middlewares.js
exports.isLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(403).send("로그인 필요");
  }
};
exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) {
    next();
  } else {
    const message = encodeURIComponent("로그인한 상태입니다.");
    res.redirect(`/?error=${message}`);
  }
};

 // isAuthenticated는 요청자의 로그인 여부를 확인하여 true, false를 반환한다.

로그인과 로그아웃 구현

이제 로그인과 로그아웃에 적어줄 코드이다.
이것만을 위해 사전준비를 왕창 했었다.

// routes/login.js
router.post("/login", isNotLoggedIn, (req, res, next) => {
  passport.authenticate("local", (authError, user, info) => {
    // local에 대한 인증 방법이다. 성공하면 user에 사용자의 데이터가 들어간다.
    if (authError) { // 인증 에러
      console.error(authError);
      return next(authError);
    }
    if (!user) { // 입력된 user가 없으면
      return res.redirect("/");
    }
    // user가 있으면 리턴
    return req.login(user, (loginError) => {
      if (loginError) {
        console.error(loginError);
        return next(loginError);
      }
      return res.redirect("/chat/room"); // 원하는 곳으로 보내줌
    });
  })(req, res, next); 
  // 미들웨어 내의 미들웨어에는 (req, res, next)를 붙입니다.
});

// logout 코드
router.get("/logout", isLoggedIn, (req, res) => {
  console.log("logout");
  req.logout();
  req.session.destroy();
  res.redirect("/");
});

추가내용

서버의 라이프 타임 동안 사용하는 res.locals와 req.locals에 대한 설명이다.
참고하는 책에서 이것을 활용하여 로그인을 구현해서 궁금해서 찾아보았다.

서버의 지역변수같은 느낌으로 이해하면 될 것 같은데 req와 res에 대해 내부적으로 어떻게 되어있는지 궁금해졌다.
https://rat2.tistory.com/18

profile
끄적끄적 코딩일기

0개의 댓글