사용자 정보는 서버의 세션에
프론트에는 세션을 조회할 수 있는 쿠키를 전달
cookie-parser
express-session
dotenv
passport
, passport-local
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const dotenv = require('dotenv');
const passport = require('passport');
const passportConfig = passport('./passport');
const db = require('./models');
const userAPIRouter = require('./routes/user');
const postsAPIRouter = require('./routes/posts');
const postAPIRouter = require('./routes/post');
dotenv.config(); // 'dotenv' 실행
const app = express();
db.sequelize.sync();
passportConfig();
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(cookieParser(process.env.COOKIE_SECRET)); // cookie 암호화 키. dotenv 라이브러리로 감춤
app.use(
expressSession({ // 옵션은 반드시 넣어줘야 한다.
resave: false, // 매번 세션 강제 저장
saveUninitialized: false, // 빈 값도 저장
secret: process.env.COOKIE_SECRET, // cookie 암호화 키. dotenv 라이브러리로 감춤
cookie: {
httpOnly: true, // javascript로 cookie에 접근하지 못하게 하는 옵션
secure: false, // https 프로토콜만 허락하는 지 여부
},
}),
);
app.use(passport.initialize());
app.use(passport.session()); // express-session 모듈 아래에 코드를 작성해야 한다. 미들웨어 간에 서로 의존관계가 있는 경우 순서가 중요
const passport = require('passport');
const db = require('../models');
const local = require('./local');
module.exports = () => {
passport.serializeUser((user, done) => {
// router의 req.login 요청이 들어오면 실행된다.
// 역할: 서버 메모리를 아끼기 위해 많은 사용자 정보 중에서 필요한 부분만 메모리에 저장하도록함. (여기에서는 id)
// 서버쪽에 [{ id: 3, cookie: 'asvxzc' }] 저장, cookie는 프론트로 보냄
return done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await db.User.findOne({
// 프론트에서 cookie를 보내면, 서버는 메모리에서 cookie와 관련된 id를 찾은 뒤 DB에서 user 정보를 불러옴.
where: { id },
});
return done(null, user); // 불러온 user 정보는 req.user에 저장
} catch (e) {
console.error(e);
return done(e);
}
});
local();
};
passport.serializeUser()
역할: 서버 메모리를 아끼기 위해 많은 사용자 정보 중에서 필요한 부분만 가벼운 객체로 바꾸어 메모리에 저장하도록함. (여기에서는 id, cookie만 추출)
passport.desirializeUser()
역할: 프론트에서 요청이 있을 때 마다 프론트에서 보내온 cookie와 서버 메모리 상의 id를 사용하여 DB에 요청 후 data를 가져와서 req.user
에 저장. 이후 route에서 req.user
객체 사용 가능.
const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const bcrypt = require('bcrypt');
const db = require('../models');
module.exports = () => {
passport.use(
new LocalStrategy(
{ // 프론트에서 req.body에 넣어주는 정보. 객체 key 값을 정확히 적어줘야한다.
usernameField: 'userId', // req.body = { userId: 'abcd', passport: 'xxx' }
passwordField: 'password',
},
async (userId, password, done) => {
try {
const user = await db.User.findOne({ where: { userId } });
if (!user) {
// 유저가 있는지 확인 후 유저가 없다면
return done(null, false, { reason: '존재하지 않는 사용자입니다.' });
}
const result = await bcrypt.compare(password, user.password);
if (result) {
// 유저가 있다면 비밀번호 확인 후 done 두 번째 인자로 유저 정보 넘김
return done(null, user);
}
return done(null, false, { reason: '비밀번호가 틀립니다.' }); // 비밀번호 틀렸을 때
} catch (e) {
console.error(e);
return done(e); // 서버 에러가 있는 경우 done 첫 번째 인자로 error 정보 넘김
}
},
),
);
};
passport strategy를 구성했다면, 서버의 router에 연결해줍니다.
const express = require('express');
const passport = require('passport');
const router = express.Router();
router.post('/login', (req, res, next) => {
// POST /api/user/login
passport.authenticate('local', (err, user, info) => {
// (err, user, info) 는 passport의 done(err, data, logicErr) 세 가지 인자
if (err) {
// 서버에 에러가 있는 경우
console.error(err);
next(err);
}
if (info) {
// 로직 상 에러가 있는 경우
return res.status(401).send(info.reason);
}
return req.login(user, loginErr => { // req.login() 요청으로 passport.serializeUser() 실행
if (loginErr) {
return next(loginErr);
}
const filteredUser = Object.assign({}, user.toJSON());
// user 객체는 sequelize 객체이기 때문에 순수한 JSON으로 만들기 위해 user.toJSON()
// user.toJSON() 하지 않으면 에러 발생
// toJSON()을 붙여주는 이유는 서버로부터 전달받은 데이터를 변형하기 때문임.
delete filteredUser.password; // 서버로부터 전달받은 데이터를 변형하지 않는다면
return res.json(filteredUser); // toJSON()을 붙이지 않고 바로 응답하여도 무방
});
})(req, res, next);
// 미들웨어(router) 내의 미들웨어(passport)에는 (req, res, next)를 붙입니다.
});
req.login()
요청으로passport.serializeUser()
실행