회원가입과 로그인
기능
- 간단한 회원가입 페이지 구현 - 이메일/비밀번호
- 이메일 형식이 올바른지 확인하기.
- 비밀번호 최소 길이 확인하기.
- 패스워드와 패스워드 확인문자가 일치하는지 확인하기.
- 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);
'백엔드 > node.js' 카테고리의 다른 글
[node] Async request handler / Pagination / PM2 (0) | 2022.10.27 |
---|---|
[node] Template Engine ( Pug ) (0) | 2022.10.25 |
[mongoDB] Express.js에서 mongooseODM 사용 (0) | 2022.10.23 |
[mongoDB] mongDB와 mongooseODM (0) | 2022.10.22 |
[node] REST API 구현, Postman 테스트 (0) | 2022.10.21 |