1. session 미들웨어
1.1 정의
- 세션(Session)은 사용자별로 상태를 유지하기 위해 사용되는 메커니즘이다.
- Express.js에서 세션을 관리하기 위해 주로 express-session 패키지를 사용한다.
- 세션 미들웨어는 클라이언트가 서버에 요청할 때마다 세션 ID를 쿠키에 저장하고, 서버는 이 ID를 통해 사용자 상태를 관리한다.
1.2 필요성
- 로그인 상태 유지: 사용자가 로그인한 상태를 유지하기 위해.
- 사용자 데이터 저장: 장바구니 정보, 사용자 설정 등.
- 보안 관리: CSRF 보호, 인증 등.
1.3 동작 방식
- 클라이언트 요청: 클라이언트가 서버에 요청을 보낸다.
- 세션 ID 확인: 서버는 요청에 포함된 세션 ID를 확인한다.
- 세션 ID 존재 시: 해당 세션 데이터를 로드한다.
- 세션 ID 미존재 시: 새로운 세션을 생성하고 세션 ID를 쿠키에 저장한다.
- 세션 데이터 접근 및 수정: req.session 객체를 통해 세션 데이터를 읽고 쓸 수 있다.
- 응답 전송: 서버는 응답과 함께 세션 ID를 쿠키에 설정하여 클라이언트로 전송한다.
2. 설치
npm install express-session
3. 사용 예시
const express = require('express');
const session = require('express-session');
const app = express();
// 세션 미들웨어 설정
app.use(session({
secret: 'yourSecretKey', // 세션 ID를 서명하기 위한 비밀 키
resave: false, // 세션을 항상 저장할지 여부
saveUninitialized: true, // 초기화되지 않은 세션을 저장할지 여부
cookie: { secure: false } // HTTPS를 사용하는 경우 true로 설정
}));
// 라우트 예시
app.get('/', (req, res) => {
if (req.session.views) {
req.session.views++;
res.send(`총 ${req.session.views}번 방문하셨습니다.`);
} else {
req.session.views = 1;
res.send('처음 방문하셨습니다. 세션이 설정되었습니다.');
}
});
app.listen(3000, () => console.log('서버가 포트 3000에서 실행 중입니다.'));
app.use(session({ settings }); 로 미들웨어를 설정하며, 각 라우트에서 session에 대한 부분을 설정하거나 받아올 수 있다.
위 코드에서 쿠키를 세팅하였기 때문에 따로 cookieParser를 사용하지 않아도 쿠키가 저장된다.
- secret: 세션 ID를 서명하는 데 사용되는 비밀 키이다. 강력하고 예측 불가능한 문자열을 사용해야 한다.
- resave: 세션을 수정하지 않아도 매 요청마다 세션을 저장할지 여부를 결정한다.
- false: 세션이 변경되지 않은 경우 저장하지 않습니다.
- true: 세션이 변경되지 않아도 저장합니다.
- saveUninitialized: 초기화되지 않은 세션을 저장할지 여부를 결정한다.
- true: 새로운 세션이 변경되지 않아도 저장된다.
- false: 새로운 세션이 변경될 때만 저장된다.
- cookie: 세션 쿠키에 대한 옵션을 설정한다.
- secure: true로 설정하면, HTTPS 연결에서만 쿠키가 전송된다. **개발 환경에서는 false**로 설정하고, **프로덕션 환경에서는 true**로 설정하는 것이 일반적이다.
4. session 미들웨어 주요 옵션 및 설정
참고로 기본적으로 메모리 저장소가 사용되지만, 프로덕션 환경에서는 Redis, MongoDB 등 외부 저장소를 사용하는 것이 권장된다.
5. 실무 예시
4.1 사용자 로그인 상태 유지
목적: 사용자가 로그인하면 세션에 사용자 정보를 저장하여, 이후 요청에서 로그인 상태를 유지.
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const app = express();
// 세션 미들웨어 설정
app.use(session({
secret: 'yourSecretKey',
resave: false,
saveUninitialized: false,
cookie: { secure: false } // HTTPS 사용 시 true
}));
app.use(bodyParser.urlencoded({ extended: true }));
// 로그인 폼 라우트
app.get('/login', (req, res) => {
res.send(`
<form method="POST" action="/login">
<label>사용자 이름: <input type="text" name="username"></label><br>
<button type="submit">로그인</button>
</form>
`);
});
// 로그인 처리 라우트
app.post('/login', (req, res) => {
const { username } = req.body;
// 실제로는 데이터베이스 검증 등이 필요합니다.
req.session.user = { username };
res.redirect('/dashboard');
});
// 대시보드 라우트 (로그인 상태 확인)
app.get('/dashboard', (req, res) => {
if (req.session.user) {
res.send(`환영합니다, ${req.session.user.username}! <a href="/logout">로그아웃</a>`);
} else {
res.redirect('/login');
}
});
// 로그아웃 라우트
app.get('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.status(500).send('로그아웃 중 에러가 발생했습니다.');
}
res.redirect('/login');
});
});
app.listen(3000, () => console.log('서버가 포트 3000에서 실행 중입니다.'));
4.2 쇼핑 카트 기능
목적: 사용자가 상품을 장바구니에 담을 때, 세션에 장바구니 정보를 저장.
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const app = express();
// 세션 미들웨어 설정
app.use(session({
secret: 'yourSecretKey',
resave: false,
saveUninitialized: true,
cookie: { secure: false } // HTTPS 사용 시 true
}));
app.use(bodyParser.urlencoded({ extended: true }));
// 상품 목록 라우트
app.get('/products', (req, res) => {
const products = [
{ id: 1, name: '상품 A', price: 1000 },
{ id: 2, name: '상품 B', price: 2000 },
{ id: 3, name: '상품 C', price: 3000 },
];
let productList = '<h1>상품 목록</h1><ul>';
products.forEach(product => {
productList += `
<li>
${product.name} - ${product.price}원
<form method="POST" action="/add-to-cart">
<input type="hidden" name="id" value="${product.id}">
<button type="submit">장바구니에 담기</button>
</form>
</li>
`;
});
productList += '</ul><a href="/cart">장바구니 보기</a>';
res.send(productList);
});
// 장바구니에 상품 추가 라우트
app.post('/add-to-cart', (req, res) => {
const { id } = req.body;
const product = { id, name: `상품 ${id}`, price: id * 1000 };
if (!req.session.cart) {
req.session.cart = [];
}
req.session.cart.push(product);
res.redirect('/cart');
});
// 장바구니 보기 라우트
app.get('/cart', (req, res) => {
const cart = req.session.cart || [];
let cartList = '<h1>장바구니</h1><ul>';
cart.forEach((item, index) => {
cartList += `
<li>
${item.name} - ${item.price}원
<form method="POST" action="/remove-from-cart">
<input type="hidden" name="index" value="${index}">
<button type="submit">제거</button>
</form>
</li>
`;
});
cartList += '</ul><a href="/products">상품 목록으로 돌아가기</a>';
res.send(cartList);
});
// 장바구니에서 상품 제거 라우트
app.post('/remove-from-cart', (req, res) => {
const { index } = req.body;
if (req.session.cart && req.session.cart[index]) {
req.session.cart.splice(index, 1);
}
res.redirect('/cart');
});
app.listen(3000, () => console.log('서버가 포트 3000에서 실행 중입니다.'));
6. cookie-parser와 express-session
6.1. cookie-parser와 express-session의 관계
6.1.1. express-session이 쿠키를 처리하는 방식
- **express-session**은 세션 ID를 쿠키에 저장하여 사용자별 세션을 관리한다.
- **express-session**은 내부적으로 쿠키를 파싱하고 설정하는 기능을 제공한다.
- 따라서, **express-session**을 사용할 때는 별도로 cookie-parser를 사용할 필요가 없다.
- 과거 버전의 **express-session**은 **cookie-parser**에 의존하여 쿠키를 파싱했다.
- 최근 버전에서는 **express-session**이 자체적으로 쿠키를 처리하게 되어, **cookie-parser**의 필요성이 줄어들었다.
6.2. 언제 cookie-parser를 사용해야 하는가?
6.2.1. 다른 쿠키를 파싱할 필요가 있을 때
- express-session 외에 추가적인 쿠키를 파싱하여 사용해야 하는 경우 **cookie-parser**를 사용한다.
- 예를 들어, 사용자 선호 설정이나 추적 쿠키 등을 처리할 때 유용하다.
6.2.2. 특정 쿠키 옵션 설정이 필요할 때
- **cookie-parser**는 쿠키의 옵션을 더 세밀하게 설정하거나 암호화할 때 사용될 수 있다.
- 예시: 서명된 쿠키나 암호화된 쿠키를 사용할 때.
6.2.3. 보안 강화가 필요할 때
- **cookie-parser**를 사용하여 쿠키의 보안 속성(예: httpOnly, secure, sameSite)을 더 세밀하게 관리할 수 있다.
'Node.js' 카테고리의 다른 글
익스프레스 웹 서버 만들기 - 미들웨어(multer) (0) | 2025.01.15 |
---|---|
익스프레스 웹 서버 만들기 - 미들웨어 확장법 (0) | 2025.01.14 |
익스프레스 웹 서버 만들기 - 미들웨어(express.static) (0) | 2025.01.14 |
익스프레스 웹 서버 만들기 - 미들웨어(morgan, cookieParser, express.json, express.urlencode) (0) | 2025.01.13 |
익스프레스 웹 서버 만들기 - 미들웨어(정의, 에러처리, next() 활용) (0) | 2025.01.13 |