본문 바로가기
공부/노드

[passport] 이해하기

by 야옹아옹 2021. 7. 7.

node.js 교과서와 passport.js Doc를 참고 해 작성된 글입니다.

Passport 인증을 사용하기 위해 필요한 3가지

  1. Authentication strategies (인증전략)
  2. Application middleware (미들웨어)
  3. Sessions (optional) (세션)

Strategy 전략

use( )를 통해 사용할 수 있다.

verify callback이 꼭 필요하다.

미들웨어

미들웨어를 사용해야한다.

app.use(passport.initialize());

passport를 초기화 하기 위해서 passport.initialize 미들웨어를 사용

app.use(passport.session());

세션 사용을 원한다면, passport.session 미들웨어를 사용

세션

세션은 유저 브라우저의 쿠키에 설립되고 유지된다.

serializeUserdeserializeUser 메서드를 통해 세션을 관리한다.


Passport 실행 과정

로그인 요청이 들어온 경우

1. 라우터를 통해 로그인 요청이 들어온다.

ex ) localhost:5000/login 페이지에서 아이디와 패스워드를 입력하고 로그인 버튼을 누른다

아이디와 패스워드가 localhost:5000/login 의 주소로 POST 메서드로 요청된다.

 

2. 로그인 POST 라우터에서 passport.authenticate( ) 메서드를 호출한다.

인증 요청을 하고싶다면 passport.authentcate 메서드를 특정 전략과 함께 호출을 하면된다.

passport.authenticate('전략', 콜백 함수)

 

3. 로그인 전략(Strategy)을 수행한다.

passport.authenticate가 정해진 전략호출하고 실행시킨다.

ex ) 로컬 전략일 경우

로컬 전략을 생성해야하는데, 로컬 전략은 passport-local 모듈을 사용한다.

설치 후, 전략을 LocalStrategy 생성자로 생성해준다.

LocalStrategy.js

const passport = require('passport');
// 전략 생성자를 passport-local에서 가져온다.
const LocalStrategy = require('passport-local').Strategy; 

// 전략 수행을 위해 필요한 것들
const bcrypt = require('bcrypt');
const User = require('../models').User;

// 만들어진 전략
module.exports = ()=>{
  passport.use(new LocalStrategy({
    // passport는 기본적으로 username와 password를 req에서 가져와 비교한다.
    // 그 설정을 바꾸기 위해서 new LocalStrategy의 첫번째 인자로
    // {
    //  usernameField: '아이디로 원하는 이름',
    //  passwordField: '비밀번호로 원하는 이름'
    // }
    usernameField: 'email',
    passwordField: 'password'
  },
  // done은 passport.authenticate('전략', 여기가 done이다 )
  async (email, password , done)=>{
    try {
      // 로그인에서 email을 받아와 DB USER 테이블에 있는지 확인한다.
      const exUser = await User.findOne({where: {email}});
      // DB에서 유저 정보를 찾아온다면
      if(exUser){
        // 비밀번호를 비교한다.
        const result = await bcrypt.compare(password, exUser.password);
        // 비밀번호가 맞을 경우
        if(result){
          done(null,exUser); // 콜백 함수에 err는 null이고 구한 유저정보를 넘긴다.
        }else{
          done(null, false, {message : '비밀번호가 일치하지 않습니다.'});
        }
        done(null,false,{message : '가입되지 않았습니다.'});
      }
    } catch (err) {
      console.error(err);
      done(err);
    }
  
  }))
}

전략을 수행하고 수행 결과에 맞는 값들을 done에 넣어 passport.authenticate의 콜백함수로 돌아간다.

전략을 성공했을 경우,done의 첫번째 인수인 error는 null, 두번째 인수로 전략을 수행하고 얻은 user정보를,

세번째 인수는 추가로 넘기고싶은 info를 값을 넣는다.

 

4. 성공 시, req.login( )가 호출

passport.authenticate()는 자동으로 req.login을 호출한다. 커스텀 콜백을 사용할 경우는 직접 호출해야함.

 

5. req.login 메서드가 passport.serializeUser 호출

 

6. passport.serializeUser가 req.session에 사용자 아이디만 저장

passport.serializeUser((user, done)=>{
    console.log('시리얼라이즈 유저',user)
    done(null, user.id); // user전부를 기억하면 용량이
  });

passport.serializeUser는 세션에 위 과정에서 받아온 user데이터를 저장한다. 단, user데이터를 전부 저장할 경우

크기가 너무 커지기때문에 id값만 뽑아서 저장한다.

7. 로그인 완료

 

로그인 이후

1. 로그인 이후에 요청이 들어온다면, 요청이 라우터에 도달하기 전에 passport.session 미들웨어가

passport.deserializeUser 메서드를 호출한다.

여기서 요청은 모든 요청을 말한다. serializeUser는 로그인 요청에서만 호출된다.

 

2. deserializeUser 가 req.session에 저장된 id 값을 이용해 DB에서 사용자 정보를 조회해온다.

  passport.deserializeUser((id,done)=>{
    console.log('디시리얼라이즈 유저', id);
    User.findOne({where: {id}})
      .then((user)=>{
        // console.log('디시리얼라이즈에서 찍히는 유저',user);
        done(null, user); // req.user 에 저장된다.
      }) // 조회한 정보를 req.user에 저장한다.
      .catch(error => done(error));
  });

id값으로 데이터베이스에서 전체 유저 정보를 찾아오는 과정

 

3. 이렇게 조회한 데이터를 req.user에 저장한다.

4. 라우터에서 req.user 객체를 사용할 수 있다.


passport 를 만드는 순서

  1. 전략을 세운다. /passport 폴더를 만들고 그 안에 전략별로 파일을 만든다.
    더보기
    ex) /passport/LocalStrategy.js - 로컬 전략 파일 예시
    // 로컬 전략
    const passport = require('passport');
    const LocalStrategy = require('passport-local').Strategy;
    const bcrypt = require('bcrypt');
    
    const User = require('../models').User;
    
    module.exports = () => {
      passport.use(
        new LocalStrategy(
          {
            // passport는 기본적으로 username와 password를 req에서 가져와 비교한다.
            // 그 설정을 바꾸기 위해서 new LocalStrategy의 첫번째 인자로
            // {
            //  usernameField: '아이디로 원하는 이름',
            //  passwordField: '비밀번호로 원하는 이름'
            // }
            usernameField: 'email',
            passwordField: 'password',
          },
          // done은 passport.authenticate('전략', 여기가 done이다 )
          async (email, password, done) => {
            try {
              // 로그인에서 email을 받아와 DB USER 테이블에 있는지 확인한다.
              const exUser = await User.findOne({ where: { email } });
              // DB에서 유저 정보를 찾아온다면
              if (exUser) {
                // 비밀번호를 비교한다.
                const result = await bcrypt.compare(password, exUser.password);
                // 비밀번호가 맞을 경우
                if (result) {
                  done(null, {user:exUser}); // 콜백 함수에 err는 null이고 구한 유저정보를 넘긴다.
                } else {
                  done(null, false, { message: '비밀번호가 일치하지 않습니다.' });
                }
                done(null, false, { message: '가입되지 않았습니다.' });
              }
            } catch (err) {
              console.error(err);
              done(err);
            }
          }
        )
      );
    };​
  2. 전략들을 관리하는 index.js파일을 만든다.
    더보기
    ex) /passport/index.js
    세션 관리를 하는 serializeUser와 deserializeUser를 만든다.
    // 모든 전략들을 관리하는 파일
    const passport = require('passport');
    const local = require('./LocalStrategy'); // 로컬 인증 전략 가져오기
    
    const User = require('../models/index').User;
    
    // 시리얼라이즈유저와 데시리얼라이즈유저 메서드
    module.exports = ()=>{
      // 세션에 정보를 기억하는 메서드
      // 로그인 요청이 발생하면 실행된다.
      passport.serializeUser((user, done)=>{
        console.log('시리얼라이즈 유저',user)
        done(null, user.id); // user전부를 기억하면 용량이
      });
      // 매 요청마다 발생한다. id는 시리얼라이즈의 user.id다.
      passport.deserializeUser((id,done)=>{
        console.log('디시리얼라이즈 유저', id);
        User.findOne({where: {id}})
          .then((user)=>{
            // console.log('디시리얼라이즈에서 찍히는 유저',user);
            done(null, user); // req.user 에 저장된다.
          }) // 조회한 정보를 req.user에 저장한다.
          .catch(error => done(error));
      });
    
      local();
    }​
  3. 로그인 라우터를 생성한다.
    더보기
    커스텀 콜백을 사용했다.
    // 로그인을 실행하는 라우터
    router.post('/',(req,res,next)=>{
      // 미들 웨어 안에서 미들웨어를 사용하기
      passport.authenticate('local',(authError,user,info)=>{
        if(authError){
          console.error(authError);
          return next(authError);
        }
        if(!user){
          return next(authError);
        }
        return req.login(user,(loginError)=>{
          if(loginError){
            console.error(loginError);
            return next(loginError);
          }
          return res.redirect('/');
        })
      })(req,res,next);
    });​
  4. 어플리케이션에 설정한다.
    app.js에 passport관련 설정을 해준다.
    더보기
    이때, session 미들웨어는 반드시 passport보다 상위 코드로 써준다. passport가 session을 이용하기 때문
    // passport 설정
    const passportConfig = require('./passport'); // index.js를 의미
    passportConfig();
    
    // 반드시 세션 미들웨어를 먼저 사용하고 passport를 사용해야한다.
    // passport를 사용하기 위해서 session이 꼭 필요하다
    app.use(
      session({
        resave: false,
        saveUninitialized: false,
        secret: process.env.COOKIE_SECRET,
        cookie: {
          secure: false,
          httpOnly: true,
        },
      })
    );
    
    
    // passport
    app.use(passport.initialize());
    app.use(passport.session());​

custom Callback

콜백을 내가 원하는 형태로 커스텀해서 사용할 수 있다.

기본적으로 사용하는 passport.authenticate를 사용하는 라우터 = 커스텀 콜백 X

app.post('/login',
  passport.authenticate('local'),
  function(req, res) {
    // If this function gets called, authentication was successful.
    // `req.user` contains the authenticated user.
    res.redirect('/users/' + req.user.username);
  });

// 또는

app.post('/login',
  passport.authenticate('local', { successRedirect: '/',
                                   failureRedirect: '/login' }));

passport.authenticate('전략')인증 요청을 실행하는 메서드다. 인증이 성공한다면 req.user전략에서 설정한 값이 들어간다.

successRedirect와 failureRedirect를 사용해 인증 성공/실패 시, 바로 원하는 페이지로 리다이렉트할 수 있다.

 

콜백을 내가 원하는 형태로 커스텀 할 수 있다.

doc에 custom Callback을 검색해보면 찾을 수 있음.

app.get('/login', function(req, res, next) {
// 여기서 함수는 전략에서의 함수 verify callback이다
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect('/login'); }

	
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/users/' + user.username);
    });
  })(req, res, next);
});

전략에서 추가적인 데이터를 info를 통해 받을 수 있다. 

 

req.login 메서드

세션을 설립한다.

Note: passport.authenticate() middleware invokes req.login() automatically. 

기본적으로 authenticate가 자동으로 req.login을 호출한다. 

단, 커스텀 콜백을 사용할 때, 세션을 설립하는 것은 어플리케이션의 몫이되어서 커스텀 콜백을 사용한다면 req.login( )을 호출하고 response를 보낸다.

 

 

댓글