반응형

회원가입과 로그인

기능

- 간단한 회원가입 페이지 구현 - 이메일/비밀번호 

- 이메일 형식이 올바른지 확인하기.

- 비밀번호 최소 길이 확인하기.

- 패스워드와 패스워드 확인문자가 일치하는지 확인하기.

- form 이용해 post 요청을 전송한다.

- 회원가입 처리 및 redirect

 

 

회원정보 DB에 저장

- 비밀번호 저장시, HASH 암호화하여 저장하여 보안 취약점 해결

- node.js에서 기본제공하는 crypto 모듈로 sha1, sha224m sha256 등 알고리즘 이용해 hash값 얻을 수 있다.

( hash: 문자열을 되돌릴 수 없는 방식으로 암호화하는 방법이다. )

// hash-password.js

const crypto = require('crypto');

module.exports = (password) => {
  const hash = crypto.createHash('sha1');
  hash.update(password);
  return hash.digest("hex"); // 16진수로 변환하여 반환한다.
}

 

 

회원 가입 기능 구현

- email, name, password 세가지 항목을 입력받은 뒤 post 요청이 도착.

- password는 따로 구현한 해시함수를 이용해 변환

- mongoose에서 제공하는 model.create({ }) 함수를 이용해 객체를 생성하여 DB에 저장

const { Router } = require('express');
const asyncHandler = require('../utils/async-handler');
const { User } = require('../models');
const hashPassword = require('../utils/hash-password');

router.post(
  '/join',
  asyncHandler(async (req, res) => {
    const { email, name, password } = req.body;
    const hashedPassword = hashPassword(password); // 비밀번호 해쉬값 만들기
    const user = await User.create({
      email,
      name,
      password: hashedPassword, //평문 비밀번호가 아닌 암호화된 비밀번호를 저장한다.
    }); // 회원 생성하기

    // 아래 코드 수정 시 오답으로 처리될 수 있습니다.
    console.log('신규 회원', user);

    res.redirect('/'); // 메일 페이지로 redirect한다.
  })
);

 

src/utils/hash-password.js

const crypto = require('crypto');

module.exports = (password) => {
  const hash = crypto.createHash('sha1');
  hash.update(password);
  return hash.digest("hex");
}

 

src/utils/async-handler.js

module.exports = (requestHandler) => {
  return async (req, res, next) => {
    try {
      await requestHandler(req, res);
    } catch (err) {
      next(err);
    }
  }
}

 


PASSPORT 

 

 

passport.js

express.js 어플리케이션에 간단하게 사용자 인증 기능을 구현하게 도와주는 패키지이다.

유저의 세션관리 및 다양한 로그인 방식을 추가할 수 있게 한다.

 

1. 폼 check (프론트엔드 쪽에서 구현)

이메일, 비밀번호 값이 들어있는지 체크. 비어있으면 굳이 비밀번호 길이 확인이나 post 요청 보내지 않아도 된다.

 

2. passport-local strategy

- new LocalStrategy 함수에 config 객체(usernameFeild와 passwordFeild)를 전달.

 

src/passport/strategies/local.js

const LocalStrategy = require('passport-local').Strategy;
const { User } = require('../../models');
const hashPassword = require('../../utils/hash-password');

const config = {
  usernameField: 'email',       // 'email' 필드 사용하도록 설정
  passwordField: 'password',    // 'password' 필드 사용하도록 설정
};

const local = new LocalStrategy(config, async (email, password, done) => {
  try {
    const user = await User.findOne({ email });
    if (!user) {
      throw new Error('회원을 찾을 수 없습니다.');
    }
    // 검색 한 유저의 비밀번호와 요청된 비밀번호의 해쉬값이 일치하는지 확인
    if (user.password !== hashPassword(password)) {
      throw new Error('비밀번호가 일치하지 않습니다.');
    }

    done (null, {
      shortId: user.shortId,
      email: user.email,
      name: user.name,
    });
  } catch (err) {
    done(err, null);
  }
});

module.exports = local;

- email 값으로 회원이 있는지 확인

- password 값으로 기존회원의 비밀번호와 일치하는지 확인 (이때 받은 평문 password를 hash함수로 변환하여 비교한다.)

 

 

3. done callback

error가 있으면 don(err, null) 전달, 아니면 사용자 정보를 전달한다.

사용자 정보를 전달할 때 user 객체 전체를 전달하면,  비밀번호 해시값 같이 중요정보까지 전달되므로 비추천한다.

done (null, user);

아래처럼 제한하여 세션에 저장되는 사용자 정보를 최소화한다. (shortId, email, name )

done (null, {
      shortId: user.shortId,
      email: user.email,
      name: user.name,
});

 

 

4. passport.use

작성한 strategy를 passport.use를 이용해 사용하도록 선언한다.

 

src/passport/index.js

const passport = require('passport');
const local = require('./strategies/local');

module.exports = () => {
  // local strategy 사용
  passport.use(local);

  passport.serializeUser((user, callback) => {
    callback(null, user);
  });

  passport.deserializeUser((obj, callback) => {
    callback(null, obj);
  });
};

세션 유저 활용하기 - serializeUser, deserializeUser

세션을 이용해 user를 사용할 때에 설정해주어야 한다. 세션에 user정보를 변환하여 저장하고 가져오는 기능을 제공한다. 

예) 회원id를 세션에 저장한 후, 사용할 때 회원정보를 DB에서 찾아서 사용한다.

이 함수들을 사용하지 않으면 passport 로그인이 동작하지 않는다.

 

 

 

5. passport.authenticate 함수

http 라우팅에 연결하면 passport가 자동으로 해당하는 strategy를 사용하는 request handler를 생성한다.

 

src/routes/auth.js

const { Router } = require('express');
const passport = require('passport');

const router = Router();

// passport local 로 authenticate 하기 (미들웨어 추가)
router.post('/', passport.authenticate('local'), (req, res, next) => {
  res.redirect('/');
});

module.exports = router;

 

6. 로그인

express-session과 passport-session()을 사용하면 passport가 로그인시 유저 정보를 세션에 저장하고 가져오는 동작을 자동으로 수행해준다. 

 

src/app.js

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const mongoose = require('mongoose');
const dayjs = require('dayjs');
const session = require('express-session');
const passport = require('passport');

const indexRouter = require('./routes');
const postsRouter = require('./routes/posts');
const authRouter = require('./routes/auth');

const loginRequired = require('./middlewares/login-required');

require('./passport')();

mongoose.connect('mongodb://localhost:27017/simple-board');

mongoose.connection.on('connected', () => {
  console.log('MongoDB Connected');
});

const app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.locals.formatDate = date => {
  return dayjs(````date````).format('YYYY-MM-DD HH:mm:ss');
};

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(
  session({
    secret: 'elice',
    resave: false,
    saveUninitialized: true,
  })
);

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

app.use('/', indexRouter);
// /posts 경로에 로그인 필수로 설정하기
app.use('/posts', loginRequired, postsRouter);
app.use('/auth', authRouter);

// catch 404 and forward to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use((err, req, res, next) => {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

 

 

 

7. req.logout 함수

passport는 req.logout 함수를 통해 세션의 로그인 정보를 삭제하여 로그아웃 기능을 구현할 수 있다.

router.get('/logout', ... {
	req.logout();
    res.redirect('/');
});

 

 

8. login 미들웨어

로그인을 필수로 설정하고 싶은 경우, 미들웨어를 사용하여 체크할 수 있다.

function loginRequired(req, res, next) {
    if (!req.user){
        res.redirect('/');
        return;
    }
    	next();
    }

app.use('/[psts', loginRequired, postsRouter);

 

 

 

 

반응형

+ Recent posts