db에 저장 시 비밀번호를 암호화해 보안해야 한다.
이 때, bcrypt 라이브러리를 사용해 간단하게 암호화 할 수 있다.
npm i bcrypt
import express from "express";
import bcrypt from "bcrypt";
const router = express.Router();
router.post("/", (req, res) => {
const hashedPassword = bcrypt.hash(req.body.password, 10);
});
숫자가 높을 수록 보안성이 좋아진다. 하지만 너무 높으면 성능이 낮아지므로 10~12를 사용한다.
passport를 사용하기 위해 app에 passport설정을 적용한다.
server/app.js
import passportConfig from './passport/index'
//passport 연결
passportConfig()
패스포트는 다양한 로그인 전략을 한번에 관리해준다.
설치하기
npm i passport passport-local
폴더 및 파일 생성
server/passport 폴더 생성
passport폴더에 index.js, local.js 파일 생성
server/passport/local.js
import passport from 'passport'
import { Strategy : LocalStrategy } from 'passport-local'
import { User } from '../models'
import bcrypt from 'bcrypt'
module.exports = () => {
//로그인 리퀘스트 정보
passport.use(new LocalStrategy({
//로그인 리퀘스트시 action 데이터로 email, password를 보냄.
//ㄴ> req.body.email, req.body.password
usernameField: 'email',
passwordField: 'password',
}, async (email, password, done) => {
//로그인 전략 짜기
try {
//User모델에서 해당 email을 찾음.
const user = await User.findOne({
where: { email : email }
});
//User모델에 해당 email이 없다면
if(!user){
//우선 done에 관련 정보를 넣어준다.
//순서대로 서버 에러, 성공여부, 클라이언트 에러
return done(null, false, { reason : '존재하지 않는 사용자입니다!'})
}
//해당 email이 있다면, 비밀번호 비교.
//리퀘스트된 password와 db에 저장된 user의 password를 비교.
//비교 했는데 일치하면 true, 일치하지 않으면 false를 리턴함.
const result = await bcrypt.compare(password, user.password);
//result가 true면,
if(result){
// 로그인 성공
//순서대로 서버 에러, 성공여부
//성공 여부에 사용자 정보를 넣어줌.
return done(null, user);
}
//비밀번호가 일치하지 않을 때,
//클라이언트 에러에 에러 정보를 넣어줌.
return done(null, false, { reason : '비밀번호가 틀렸습니다.' });
//서버에러시
} catch(err) {
//서버에러 여부에 error를 넣어줌.
console.error(err);
return done(err);
}
}));
};
로그인 전략은 서버 에러 여부를 try, catch로 나누고,
서버 에러가 없을 경우, 이메일 있는지 검사 -> 비밀번호 일치하는지 검사의 flow로 로그인 해준다.
만들어 놓은 passport를 사용해 api서버에서 로그인 검증을 할 수 있다.
server/routes/login.js
import passport from 'passport'
//done(서버에러, 성공여부, 클라이언트에러)를 받아옴.
router.post('/login', passport.authenticate('local', (ctErr, userInfo, svrErr) => {
//서버에러 처리
if(err){
console.error(err);
}
});
만들어 놓은 로그인 전략이 실행됨.
미들웨어 확장은 express의 기법이다.
passport.authenticate를 사용하면 express의 req,res,next기능을 사용하지 못하는데 이를 덮어씌움으로써 req,res,next를 사용할 수 있게 해준다.
router.post('/login', (req,res, next)=> {
passport.authenticate('local', (ctErr, userInfo, svrErr) => {
//서버 에러시
if(svrErr){
console.error(err);
return next(err);
}
//클라이언트 에러시 (이메일 또는 비밀번호 틀렸을 때)
if(ctErr){
//401은 허가되지 않음을 의미. (비인증)
return res.status(401).send(ctErr.reason);
}
//로그인 성공시 user에 사용자 정보가 들어있음.
//req.login으로 passport를 통한 로그인을 할 수 있음. (passport기능)
return req.login(userInfo, async(passPortErr) => {
//passport 로그인 시 생기는 에러 핸들링.
if(passPortErr){
console.error(passPortErr);
return next(passPortErr);
}
//사용자 정보 프론트로 넘기기.
return res.status(200).json(userInfo);
})
})(req, res, next);
});
로그인 시 브라우저/서버에서 같은 유저 정보를 갖고 있어야 한다.
서버에서 로그인된 유저의 정보를 브라우저로 보내주는데, 이 때 암호화된 문자열을 쿠키에 담아 보내준다.
그리고 이 쿠키는 브러우저에서 서버로 api를 요청할 때 서버에서 로그인된 유저가 누군지 판별하기 위해 사용된다.
설치하기
npm i express-session
npm i cookie-parser
세션/쿠키를 설정할 때, secret문자열의 경우 중요한 보안 사항이므로
dotenv를 사용해 준다.
.env
COOKIE_SECRET = 'secretKey'
server/app
//dotenv 세팅
const dotenv = require('dotenv');
dotenv.config();
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
saveUninitialized: false,
resave: false,
secret: process.env.COOKIE_SECRET,
});
app.use(passport.initialize());
app.use(passport.session());
서버에서는 로그인된 유저의 모든 정보 데이터를 갖고 있으면 메모리가 너무 많이 소모된다.
이를 해결하기 위해 serialize를 사용한다.
세션에서 유저 정보의 id만 갖고 있음. => serializeUser
user의 id를 원래대로 복원함. => deserializeUser
server/passport/index.js
const passport = require('passport');
const local = require('./local');
const { User } = require('../models');
module.exports = () => {
//서버 세션에 유저의 id만 저장함.
passport.serializeUser((user, done) => {
done(null, user._id);
});
//id로 사용자 정보 복원하기.
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findOne({ where : { id }});
//user는 req.user로 들어감.
//이 req.user는 유저 정보가 필요할 때 api에서 사용됨.
done(null, user);
} catch(error) {
console.error(error);
done(error);
}
});
local();
};