블로그 프로젝트 - 2-5 : 주요 개발 내용 - Passport

HK_Jang·2022년 4월 13일
0

블로그 프로젝트

목록 보기
8/10

1. Passport

1. 무엇인가?

Passport는 Express환경에서 Session을 활용한 로그인 및 Authentication, 즉 유저 인증을 도와주는 미들웨어 패키지이다.

2. 왜 활용했는가?

현재 프로젝트에서 게시물 수정/삭제등의 권한은 Admin에만 있어야 하고, 현재는 Local DB의 인증 정보만을 활용하지만, 추후 댓글 기능 등을 추가하게 되면 어드민 뿐 아니라 일반 유저의 로그인을 받아야 하는데, 이 때 타 사이트 로그인 정보를 활용한 인증으로의 확장이 가능했기 때문에 Passport를 이용하게 되었다.

3. 어떻게 활용했는가?

  1. 먼저 MySQL을 활용한 SessionDB 설정을 진행하였다. MySQL을 활용, 외부에 Session정보를 저장하게 되면 서버가 예기치 못하게 종료되거나 사용자가 로그아웃을 하지 않고 종료하는 등의 경우에도 Session정보가 없어지지 않고 그대로 DB에 남아 유저가 Session 정보를 계속 활용 가능하다.

SessionDB.js

var session = require('express-session');
// session store를 위한 db 접근 express-mysql-session 객체 생성 후 전달
var MySQLStore = require('express-mysql-session')(session);

var options = {
  host: '', // DB 위치한 호스트
  port: 0000, // 및 포트
  user: '', // 유저명
  password: '', // 비밀번호
  database: '' // 데이터베이스 명
};

var sessionStore = new MySQLStore(options);
module.exports = sessionStore;
  1. passport 관련 설정을 진행한다.

passport.js

/* 
for mysql passport
args : app : express() object
*/
module.exports = function(app){
    const usermodel = require('../model/user');
    const passport = require('passport');
    const LocalStrategy = require('passport-local').Strategy;

    app.use(passport.initialize());
    app.use(passport.session());

    passport.serializeUser(function(user, done){ // 세션에 저장할 정보.
        done(null, user);
    })

    passport.deserializeUser(function(id, done){ // 페이지마다 세션에서 보일 정보.
        done(null, id);
    })

    passport.use(new LocalStrategy(
        {
        usernameField:'inputId', passwordField:'inputPassword'},
            async function(username, password, done){
                var result = await usermodel.authenticate(username, password);
                if(result.length === 0){
                    return done(null, false, {message : 'Access Denied.'});
                } else {
                    return done(null, result[0].id);
                }
        }));
    return passport;
}

이 때 유의할 점은 다음과 같다.

  • passport는 express에 미들웨어 형태로 설치해야 함
    • 그러나 가독성을 위해 따로 파일을 분리했으므로, 인자로 express() 객체를 받아야 함
  • serializeuser에서 설정한 이름으로 DB에 저장이 됨.

serializeUser함수

 passport.serializeUser(function(user, done){ // 세션에 저장할 정보.
        done(null, user);
    })

MariaDB의 sessions 테이블

  • deserializeUser에서 설정한 이름으로 세션에 저장됨.
    • 메인 화면 즉 indexView를 위해 user 세션에 접근하는 모습

boardController.js

const indexView = async(req, res) => {
    ...
    adminLogin = false;
    if(req.user === 'admin'){ // 세션에 접근하는 모습
        adminLogin = true;
    }
    ...
};
  • 로그인 시 MariaDB를 활용해 유저 정보 비교를 진행한다.
    • id는 평문저장 하였으나, 비밀번호는 MySQL의 SHA2 함수를 통해 AES256 암호화하여 저장했다.
    • LocalStrategy 객체를 통해 실질적으로 로그인 성공 / 실패시 동작을 결정한다.

Authentication을 위한 LocalStrategy 세팅.

    passport.use(new LocalStrategy(
        {
        usernameField:'inputId', passwordField:'inputPassword'}, 
        // HTML단에서 inputId로 id 필드 정보를, passwordField를 통해 password 피드 정보를 넘긴다.
            async function(username, password, done){
                var result = await usermodel.authenticate(username, password); 
                if(result.length === 0){ // 실패시
                    return done(null, false, {message : 'Access Denied.'});
                } else { // 성공시
                    return done(null, result[0].id);
                }
        }));

userModel.js

User.authenticate = function(userId, userPassword){
    return new Promise((resolve, reject) => {
        db.query("SELECT * FROM user WHERE id=? AND password=SHA2(?,256)",[userId, userPassword], function(err, result, fields){
            if (err) throw err;
            else {
                resolve(result);
            }
        })
    }).catch(error => console.log(error));
}
  1. 라우터를 통해 session 및 Passport를 설치했다 이 때, 유의할 점은 다음과 같다.
  • passport.js를 불러올 때 express()객체 넘겨주기
  • 생성된 passport객체를 라우터에 넘겨주기

index.js (서버가 설치된 장소)

const sessionStore = require('./lib/sessionDB.js');
// 1에서 설정한 sessionDB 

app.use(session({
  key: 'nickname',
  secret: 'mysecret',
  store: sessionStore,
  resave: false,
  saveUninitialized: false,
  cookie : {maxAge: 24 * 60 * 60 * 1000} // (1000 * 60 * 60 * 2) ms => 2hrs 
}));
// Session 설치
const passport = require('./lib/passport')(app);
app.use('/admin', require('./routes/admin')(passport));
// 로그인을 받을 화면에 passport 객체 전달

/routes/admin.js (라우터)

const router = express.Router();

module.exports = function(passport){ // passport 자체를 객체로 받아야 하기 때문에 해당 함수 형태를 가짐.

    router.get('/login', loginView);

    router.post('/loginProcess', passport.authenticate('local',{
            successRedirect : '/',
            failureRedirect: '/admin/login',
            failureFlash: true
        })
    );

    router.get('/logoutProcess', logoutProcess);

    return router;
};
  1. 로그인 HTML을 만들어 loginProcess로 정보를 넘겨준다.

loginView.ejs

<form class="form-signin" action="/admin/loginProcess" id="signinForm" method="post">
          <h1 class="h3 mb-3 font-weight-normal">Admin sign in</h1>
          <label for="inputID" class="sr-only">ID</label>
          <input type="text" name="inputId" id="inputID" class="form-control" placeholder="ID" required autofocus>
          <label for="inputPassword" class="sr-only">Password</label>
          <input type="password" name="inputPassword" id="inputPassword" class="form-control" placeholder="Password" required>
          <div class="checkbox mb-3">
          </div>
          <button class="btn btn-lg btn-primary btn-block" type="submit" id="signinBtn">로그인</button>
</form>

routes/admin.js

...
router.post('/loginProcess', passport.authenticate('local',{
            successRedirect : '/', // 성공시 redirect
            failureRedirect: '/admin/login', // 실패시 redirect
            failureFlash: true
        })
    );
...
  1. 위와 같은 과정을 통해, 로그인 성공 시 main 화면으로 리다이렉트 되며 세션에 로그인 관련 정보가 남는다.
  2. 접근 제어는 다음과 같이 설정했다.
  • Controller단에서 세션에 접근한 후, 세션에 있는 정보가 유효하면 ejs를 렌더링할 때 bool 변수를 넘겨준다.

boardController.js

const indexView = async(req, res) => {
    post = await indexmodel.aboutmeAll();
    adminLogin = false;
    if(req.user === 'admin'){ // 세션에 접근
        adminLogin = true; // 유효하면 true
    }
    // 홈페이지 소개글 전부 select해서 index.ejs로 pass
    res.render("index", {post, adminLogin}); // ejs 렌더링시 bool변수 넘김
    // render
};

index.ejs

<ul class="nav nav-pills">
    <li class="nav-item"><a href="/" class="nav-link active" aria-current="page">About Me</a></li>
    <li class="nav-item"><a href="project" class="nav-link text-body">Projects</a></li>
    <%if(adminLogin) {%> <!-- 접근 제어 부분. -->
        <li class="nav-time"><a href="index_admin" class="nav-link text-body">Modify Index</a></li>
        <%}%>
        </ul>
  1. 적용된 모습은 다음과 같다.

login 화면

로그인 전 게시판 화면

로그인 후 게시판 화면. 글 편집이 가능한 모습을 볼 수 있다.

profile
살아남는 종은 강한 종이나 똑똑한 종이 아닌, 변화에 적응하는 종이다. - 찰스 다윈

0개의 댓글