본문 바로가기

Node.js

[Node] passport

이전에는 세션을 이용한 기본적인 로그인을 구현하였었다.

 

그래서 이번에는 express의 미들웨어인 passport를 이용하여 로그인 기능을 구현하고자 한다.

 

일단 passport를 이용하기 위해선 패키지 설치부터 해야한다.

 

npm i passport passport-local express-session

 

여기서 express-session을 설치하는 이유는 passport가 내부적으로 세션을 이용하기 때문이다.

바로 뒤에 설명할 passport 설정에서도 session 설정 뒤에 passport 설정이 와야하는 이유이기도 하다.

 

이번에 passport를 사용함에 있어서는 MySQL을 사용하도록 하겠다.

CREATE TABLE user (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);

 

위와 같이 테이블을 생성해두겠다.

 

이제 passport의 설정을 해주어야한다.

 

<server/index.js>

const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const FileStore = require('session-file-store')(session);

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(session({
    secret: 'my_session_key',
    resave: false,
    saveUninitialized: false,
    store: new FileStore(),
}));

//세션 설정 뒤에 적용
app.use(passport.initialize());
app.use(passport.session());

 

 

이제 설정도 끝났으니 로직을 구성해야한다.

 

passport-config.js 라는 파일을 만들어 분리해주겠다.

<passport-config.js>

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const db = require('../handlers/DBinfo');

passport.use(new LocalStrategy({
    usernameField: 'username',  //전달받은 아이디, 여기서 ''안의 이름과 로그인 폼의 아이디 input의 name 값이 일치해야한다.
    passwordField: 'password'
},
    function (username, password, done) {
        const query = `
        SELECT 
        user.*, user_info.* 
        FROM coursing.user 
        LEFT JOIN coursing.user_info ON user.id = user_info.user_id 
        WHERE username = ?;
        `;

        db.query(query, [username], (err, results) => {
            if (err) {
                console.error('Database query error:', err);
                return done(err);
            }
            //쿼리문을 이용해 반환값이 없으면 일치하는 아이디가 없는 것
            if (results.length === 0) {
                return done(null, false, { message: 'Incorrect username.' });
            }
            //이외의 경우(일치하는 아이디 있음)
            const user = results[0];

            if (password !== user.password) {
                console.log("Incorrect password");
                return done(null, false, { message: 'Incorrect password.' });
            }

            return done(null, user);
        });
    }
));

module.exports = passport;

 

가장 먼저 클라이언트로부터 아이디와 비밀번호를 전달받아야한다.

전달받을 필드를 설정하되, 필드 이름과 전달 객체의 이름이 같아야한다.

 

필드가 설정되면, DB에서 아이디와 비밀번호를 검색한다.

일치하는 값이 있으면 최종적으로 return done(null, user); 를 통해서 인증에 성공함을 알린다.

 

이제 서버에서는 처리가 완료되었으니, 이 결과를 클라이언트에 전달해주어야한다.

 

//routes.js
const express = require('express');
const router = express.Router();
const postLogin = require('../handlers/authHandler');

router.post('/login', postLogin);

//authHandler.js
const passport = require('../passport/passport-config');

const postLogin = (req, res, next) => {
    passport.authenticate('local', (err, user, info) => {
        if (err) {
            return next(err);
        }
        if (!user) {
            return res.status(401).json({ message: '인증 실패' });
        }
        req.logIn(user, (err) => {
            if (err) {
                return next(err);
            }
            return res.status(200).json({ message: '인증 성공' });
        });
    })(req, res, next);
}

module.export = postLogin;

 

이렇게 하면 이제 요청에 의한 응답이 완료된다.

다만, 로그인은 일시적으로 보여주는 것이 아니기 때문에 지속될 수 있는 값으로 남아야한다.

이전에 세션 로그인을 이용했던것과 동일하게 passport를 이용한 로그인 처리에도 세션이 이용된다.

 

<passport-config.js>

...
//로그인시에 유저 고유아이디 등의 값을 이용해 로그인 토큰 역할을 하도록 저장.
passport.serializeUser((user, done) => {
  done(null, user.id);
});

//로그인 토큰을 이용해 DB로부터 유저 데이터를 제공.
passport.deserializeUser((id, done) => {
  const query = 'SELECT * FROM coursing.user WHERE id = ?';
  db.query(query, [id], (err, results) => {
    if (err) {
      return done(err);
    }
    done(null, results[0]);
  });
});

...

 

serialize와 deserialize가 사용되는데, 1차적으로는 serialize가 필요하다.

이는 단순히 방금 설명한 로그인 처리에 대한 결과를 저장해두는 '로그인 토큰'과 같은 역할이다.

여기서는 user.id의 값을 활용했는데, 이처럼 아무 정보가 아닌 어떤 사용자의 로그인 정보인지에 대한 구분이 가능해야한다.

그 이유는 deserialize의 역할이 유저 데이터의 요청이 들어오면 로그인 토큰을 이용해 DB로부터 해당 유저의 데이터를 찾아 제공하는 것이기 때문이다.

 

여기까지 하면 세션을 이용해 로그인을 처리하는 부분까지 구현이 완료된다.

 

마지막으로 로그아웃에 대한 처리를 해주어야한다.

 

로그아웃 요청이 들어오면 기존의 로그인에 대한 반대 처리를 해주어야하는데, passport를 통해서 한 작업중에 남아있는 데이터는 오직 세션에 있는 로그인 토큰밖에 없다.

그렇다보니 세션에 있는 데이터를 삭제해주면 간단히 처리된다.

 

//routes.js
...

router.post('/logout', postLogout);

//authHandler.js
...

const postLogout = (req, res) => {
    req.logout((err) => {
        if (err) {
            return res.status(500).json({ message: '로그아웃 실패', error: err });
        }
        req.session.destroy((err) => {
            if (err) {
                return res.status(500).json({ error: err });
            }
            res.status(200).json({ message: '로그아웃 성공' });
        });
    });
};

module.exports = { postLogin, postLogout };

 

세션 삭제는 session.destroy로 가능하다.

 

 

 

 

 

솔직히 이렇게 간단한 기능만을 구현하다보니 passport를 이용해야하는 이유가 잘 와닿지 않는다.

세션을 이용한 로그인을 구현하는 것이 설정부터 로직 구현까지 훨씬 더 간단했다.

소셜 로그인을 이용할 수 있다는 장점이 있기는 하다.

인증에 있어서 보안에 대한 장점도 있다고 하는데, 거기까지는 공부해보지 않아서 모르겠다.

 

 

 

'Node.js' 카테고리의 다른 글

[Node.js] Google Vision Api OCR / 텍스트 추출하기 / Tesseract?  (0) 2024.12.11
[Node.js] Multer  (0) 2024.11.20
[express] Session  (0) 2024.06.04
[Node] Axios  (0) 2024.02.28
[Node] Body-Parser  (1) 2024.02.27