#2. Passport js와 Local/JWT Strategy

toto9602·2022년 2월 23일
0

첫 Express 프로젝트

목록 보기
3/7

참고자료: passport, localStrategy의 동작에 대해 이해하기
참고자료: PassportJs + JWT로 유저 인증하기
참고자료: Passport로 회원가입 및 로그인하기
참고자료: passport js 홈페이지

이번 글에서는 인증 기능 관련해서 사용하고 있는 passport js를 이용한 인증 방식에 대해 적어 보기로!

사실 passport js는 쓰기로 해 놓고도 작동 방식을 정확히 몰라 쓰는 내내 골머리를 썩였는데..
일단은 아는 거에 한해서 적어 보기로 했다..!

사실 아직도 동작 흐름을 정확히 아는 건 아닌 듯..

nodeJs에서 인증을 도와주는 미들웨어라는 Passportjs는 Strategy(전략)이라는 개념을 사용해서 인증을 진행한다고 한다!

그래서 이 글의 순서는

  1. 인증 관련 함수 작성하기
  2. 옵션 & 인증 관련 함수로 Strategy 등록하기
  3. router에 적용하기

정도가 될 듯!!

P.S. 함수 작성 전에 passport, passport-local, passport-jwt 등 필요한 패키지를 설치해 주었다!

1. 인증 관련 함수 작성하기

passport/passport.js

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy; 
const {ExtractJwt, Strategy:JWTStrategy} = require('passport-jwt'); 
const {User} = require('../models/index')
const bcrypt = require('bcrypt'); //암호화
require('dotenv').config();

const loginVerify = async (email, password, done) => {
    try {
        const user = await User.findOne({where:{email:email}});

        if (!user) {
            return done(null, false, {message:'존재하지 않는 사용자입니다.'})
        }
        const isAuth = await bcrypt.compare(password, user.password)
        if (!isAuth) {
            return done(null, false, {message:'잘못된 비밀번호입니다.'})
        } else {
            return done(null, user);
        }
    } catch(error) {
        console.log(error);
    }
}

const JWTVerify = async (jwtpayload, done) => {
    User.findOne({where:{pk:jwtpayload.pk}})
                .then(user => {
                    return done(null, user);
                })
                .catch(error => {
                    return done(error);
                })
}

passport-localpassport-jwt에서 필요한 부분을 가져와 주고,
인증 함수를 위와 같이 작성하였다.

LoginVerify에서는 이메일, 비밀번호, 그리고 done 함수를 인자로 받아서
각각 이메일과 비밀번호로 검사!

그리고 결과에 따라 done 함수를 반환한다.
done 함수가 인자로 무엇을 받는지, 어떤 역할을 하는지.. 감이 안 잡혔었는데
글 최상단에 기재한 세 번째 참고자료가 도움이 많이 되었다..!!

간단히 정리하면,

  • 첫 번째 인자는 무조건 실패하는 경우에 사용
  • 두 번째 인자는 성공할 때 반환할 값
  • 세 번째 인자는 에러를 사용자가 임의로 처리하고 싶을 때 작성하는 부분

JWTVerify는 jwt의 내용과, 마찬가지로 done 함수를 인자로 갖는다.
jwt에 사용자의 pk와 email 정보를 담을 예정이라, jwt에 담긴 정보 중 pk로 사용자를 검색하여, 존재하는 사용자이면 user를 반환하게 작성하였다.

2. 옵션 & 인증 관련 함수로 Strategy 등록하기

passport/passport.js

module.exports = () => {
    //Local Strategy
    passport.use('local',
        new LocalStrategy({
            usernameField:'email',
            passwordField:'password',
            passReqToCallback:false,
        }, loginVerify)
    );

    //JWT Access Strategy
    passport.use('jwtAccess',
        new JWTStrategy({
            jwtFromRequest:ExtractJwt.fromAuthHeaderAsBearerToken(),
            secretOrKey:process.env.JWT_SECRET_ACCESS_KEY,
            passReqToCallback:false,
        }, JWTVerify)
    );
    //JWT Refresh Strategy
    // passport.use('jwtRefresh',
    //     new JWTStrategy({
    //         jwtFromRequest:ExtractJwt.fromHeader('refresh'),
    //         secretOrKey:process.env.JWT_SECRET_REFRESH_KEY
    //     }, JWTRefreshVerify)
    // );
};

같은 파일에 아래 부분에 다음과 같이 작성했다.

local이라는 이름으로 설정한 LocalStrategy에서
passport.use()의 첫 번째 인자로 해당 전략의 옵션을 작성해 주었다.
이메일을 username으로 사용할 예정이라 usernameField, passwordField를 다음과 같이 작성했고,
passReqToCallback을 false로 작성했다.

해당 부분을 true로 설정하면,
#1 에서 작성했던 loginVerify의 인자가
(req, email, password, done)으로 바뀌어야 한다!
즉, callback인 loginVerify에서 req를 사용할 수 있게 되는 것!

jwtAccess, 즉 Access Token 인증에 사용할 JWTStrategy에서는
ExtractJwt.fromAuthHeaderAsBearerToken()을 사용해서 요청에 담겨있는 JWT를 가져오는 듯!

  1. Postman에서 아래와 같이 Auth 탭에서 Bearer Token을 선택하고, Token 자리에 JWT를 넣거나
  2. 프런트엔드 등에서 요청을 보낼 때는 Authorization을 키로 두고, value에
    Bearer {ACCESS_TOKEN}과 같이 입력하면 정상적으로 받아짐!

RefreshToken을 바탕으로 AccessToken을 신규 발급하는 api에서 사용해 보려고 RefreshToken에 사용할 전략도 만들어 두었는데, 일단은 특별히 사용할 일이 없는 것 같다.

3. router에 적용하기

해당 passport 전략을 적용한 파일 중.. authRouter와 dropRouter는 다음과 같이 작성했다.
(placeRouter는 최근에 추가하여 전반적으로 한 번 수정을 해야 할 것 같아 일단 생략)

routes/authRouter.js

const router = require('express').Router();
const passport = require('passport');
const controller = require('../controllers/authController');

const LocalPassportAuth = passport.authenticate('local', {session:false}); //id, pw 검증

router.post('/signup', controller.signUp, LocalPassportAuth, controller.logIn); //회원가입
router.post('/login', LocalPassportAuth, controller.logIn); //로그인_accessToken, refreshToken 발급
router.post('/token/refresh', controller.tokenRefresh) //AccessToken이 만료되면, refreshToken보내서 AccessToken 재발급
router.post('/logout', controller.logOut) //로그아웃
module.exports = router;

controller와 services 관련 로직은 다음 글에서 작성할 예정!

routes에서는 passport.authenticate() 의 첫 번째 인자로,
전략의 이름 == passport.use() 의 첫 인자를 적어준다.

Passport는 인증에 성공하면 자동으로 세션을 생성하는데, 이 부분을 사용하지 않을 것이라면
{session:false} 옵션을 추가하여 비활성화해 줄 수 있다!

session을 사용하게 되면 app.js 쪽에서 serializeUser, deserializeUser 등을 추가로 작성해 줘야 하는 듯!

router.post('/signup', controller.signUp, LocalPassportAuth, controller.logIn);

회원가입 api의 경우, 회원가입 후 해당 데이터로 로그인 인증을 거쳐 바로 logIn controller까지 이어질 수 있도록 작성했다.

router.post('/login', LocalPassportAuth, controller.logIn);

로그인은 passport로 인증을 거친 후, logIn 컨트롤러에서 인증된 요청을 받아 응답을 반환!


router.post('/token/refresh', controller.tokenRefresh)

해당 api로 접근하면, accessToken과 refreshToken을 받아 accessToken이 만료되었고 refreshToken이 유효하면 새로운 accessToken을 발급한다.

(원래 이 api에 jwtRefresh Strategy를 넣어 두었는데, 굳이 필요가 없는 듯해 일단 빼 둠)


router.post('/logout', controller.logOut)

다음 글에서 작성하겠지만, logOut api는 DB에 저장된 사용자의 refreshToken을 삭제하는 로직을 거친다.

refreshToken을 DB에 저장하는 게 맞는지는 계속 고민..

routes/dropRouter.js

const router = require('express').Router();
const passport = require('passport');
const controller = require('../controllers/dropController');

const jwtpassportAuth = passport.authenticate('jwtAccess', {session:false});

router.post('/', jwtpassportAuth, controller.newDrop);
router.get('/', jwtpassportAuth, controller.getDrops);
router.put('/:pk', jwtpassportAuth, controller.updateDrop);
router.delete('/:pk', jwtpassportAuth, controller.deleteDrop);
module.exports = router;

이 프로젝트에서 '포스팅' 내지는 '글' 정도의 의미를 갖는 drop 관련 router에서는
CRUD 전 과정에서 AccessToken이 유효한지를 검증하고, controller로 접근한다!


해당 router들은

routes/index.js

const router = require("express").Router();


const drop = require("./dropRouter");
const auth = require("./authRouter");
const place = require('./placeRouter');

router.use("/drops", drop);
router.use("/auth", auth);

module.exports = router;

index.js 파일에 모이고..!

app.js

const express = require('express');
const app = express();
const router = require('./routes/index');

...
app.use('/', router);

app.js 코드 중 위에 작성한 일부의 코드로 사용된다 :)


다음 글부터는 router로 연결되는 controller 및 service 로직 관련 정리를 시작할 예정!

profile
주니어 백엔드 개발자입니다! 조용한 시간에 읽고 쓰는 것을 좋아합니다 :)

0개의 댓글