본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.
express-session은 Express에서 세션 기능을 쉽게 구현하기 위한 미들웨어
세션 ID를 클라이언트에게 발급하고, 이 세션 ID를 통해 서버는 클라이언트의 상태를 추적함
즉, 클라이언트가 세션 ID를 발급받은 후에는 모든 서버 요청마다 세션 ID가 포함된 쿠키를 전달하게 되며. 이로 인해 서버는 클라이언트를 쉽게 식별할 수 있음
import expressSession from 'express-session';
app.use(
expressSession({
secret: process.env.CUSTOMIZED_SECRET_KEY, // 세션 암호화 비밀 키
resave: false, // 클라이언트 요청이 올 때마다 세션을 새롭게 저장할 지 설정, 변경 사항이 없어도 다시 저장함
saveUninitialized: false, // 세션이 초기화되지 않았을 때 세션을 저장할 지 설정
cookie: {
// 세션 쿠키 설정
maxAge: 1000 * 60 * 60 * 24,
},
}),
);
/** 세션 등록 API **/
app.post('/sessions', (req, res, next) => {
const { userId } = req.body;
// 클라이언트에게 전달받은 userId를 세션에 저장
req.session.userId = userId;
return res.status(200).json({ message: '세션을 설정했습니다.' });
});
/** 세션 조회 API **/
app.get('/sessions', (req, res, next) => {
return res.status(200).json({
message: '세션을 조회했습니다.',
session: req.session.userId ?? null, // 세션에 저장된 usrId를 조회
});
});
// import JWT from 'jsonwebtoken';
import { prisma } from '../utils/prisma/index.js';
export default async (req, res, next) => {
try {
// 클라이언트로부터 쿠키를 전달 받음
const { authorization } = req.cookies;
// 쿠키가 Bearer 토큰 형식인지 확인
// 배열 구조 분해 할당
const [tokenType, token] = authorization.split(' ');
if (tokenType !== 'Bearer') throw new Error('토큰 타입이 일치하지 않습니다.');
// 서버에서 발급한 JWT가 맞는지 검증
const decodedToken = JWT.verify(token, process.env.JWT_SECRET_KEY);
const userId = decodedToken.userId;
// JWT를 통해 얻은 userId를 통해 실제로 사용자가 있는지 확인 (타입 주의)
const user = await prisma.users.findFirst({
where: { userId: +userId },
});
if (!user) {
// 특정 쿠기를 삭제 (초기화)
// 쿠키에서 가져온 토큰이 잘못된 토큰이기 때문에 그냥 쿠키를 비워버림
res.clearCookie('authorization');
throw new Error('토큰 사용자가 존재하지 않습니다.');
}
// req.user에 조회된 사용자 정보 할당
req.user = user;
// 다음 미들웨어를 실행
next();
} catch (err) {
res.clearCookie('authorization'); // 특정 쿠기를 삭제 (초기화)
switch (err.name) {
case 'TokenExpiredError':
return res.status(401).json({ message: '토큰이 만료되었습니다.' });
case 'JsonWebTokenError':
return res.status(401).json({ message: '토큰 인증에 실패했습니다.' });
default:
return res.status(401).json({ message: err.message ?? '비정상적인 요청입니다.' });
}
}
};
// import JWT from 'jsonwebtoken';
import { prisma } from '../utils/prisma/index.js';
export default async (req, res, next) => {
try {
// express-session을 통해 위 과정을 간략화
const { userId } = req.session;
if (!userId) throw new Error('로그인이 필요합니다.');
// JWT를 통해 얻은 userId를 통해 실제로 사용자가 있는지 확인 (타입 주의)
const user = await prisma.users.findFirst({
where: { userId: +userId },
});
if (!user) throw new Error('토큰 사용자가 존재하지 않습니다.');
// req.user에 조회된 사용자 정보 할당
req.user = user;
// 다음 미들웨어를 실행
next();
} catch (err) {
switch (err.name) {
default:
return res.status(401).json({ message: err.message ?? '비정상적인 요청입니다.' });
}
}
};
express-mysql-session 모듈은 express-session의 정보를 MySQL에 저장할 수 있도록 도와주는 모듈
기존의 express-session은 서버를 재실행할 때마다 세션 정보가 초기화 되었음
이는 express-session이 기본적으로 세션 정보를 인 메모리에 저장하기 때문에 발생함
이를 해결하기 위해서 외부 세션 스토리지를 사용해서 서버가 재실행되어도 세션 정보가 남아있도록 함
express-mysql-session 모듈을 설치해서 MySQL를 외부 세션 스토리지로 사용하여, 세션 정보를 MySQL에 저장하고 관리할 수 있도록 할 수 있음
import express from 'express';
import cookieParser from 'cookie-parser';
import expressSession from 'express-session';
import expressMySQLSession from 'express-mysql-session';
import dotenv from 'dotenv';
import UsersRouter from './routes/users.router.js';
import PostsRouter from './routes/posts.router.js';
import CommentsRouter from './routes/comments.router.js';
import logMiddleware from './middlewares/log.middleware.js';
import errorHandingMiddleware from './middlewares/error-handing.middleware.js';
dotenv.config();
const app = express();
const SERVER_PORT = process.env.SERVER_PORT;
// 외부 세션 스토리지를 사용하기 위한 설정
const MySQLStorage = expressMySQLSession(expressSession);
const sessionStore = new MySQLStorage({
// 어떤 MySQL을 사용할지 설정
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
host: process.env.MYSQL_HOST,
port: process.env.MYSQL_PORT,
database: process.env.MYSQL_NAME,
expiration: 1000 * 60 * 60 * 24,
createDatabaseTable: true,
});
app.use(logMiddleware);
app.use(express.json());
app.use(cookieParser());
app.use(
expressSession({
secret: process.env.CUSTOMIZED_SECRET_KEY,
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
maxAge: 1000 * 60 * 60 * 24,
},
}),
);
app.use('/api', [UsersRouter, PostsRouter, CommentsRouter]);
app.use(errorHandingMiddleware);
app.listen(SERVER_PORT, () => {
console.log(SERVER_PORT, '포트로 서버가 열렸어요!');
});
강의 시청 및 정리가 끝났기에 개인 과제 진행
강의에서 진행한 프로젝트 기반으로 각종 미들웨어 및 API 구현하기
예비군으로 밀린 강의를 시청함
원래는 2주차를 오늘 안에 다 시청하려고 했으나, 생각보다 내용이 어려웠음
완전히 막힌다기 보다는 내용이 어려워서 검색하면서 이해하느라 진행이 느렸음
주말을 이용해서 결국 강의 시청 완료함