🔐
EPISODE 02
SameSite 쿠키 · CSRF 토큰 · 세션 vs JWT · httpOnly
CSRF 방어와 인증
CSRF 공격 시나리오, SameSite 쿠키와 CSRF 토큰 두 가지 방어법, 세션과 JWT의 동작 차이, 그리고 httpOnly·secure·sameSite·maxAge 보안 쿠키 속성을 익힙니다.
CSRFJWTsessioncookie
소요 시간
⏱ 45~60분
난이도
📊 중급
선수 조건
🎯 sec-01
결과물
안전한 인증 쿠키와 CSRF 토큰을 가진 폼
이 강의에서 배우는 것
- 1CSRF 공격 시나리오를 설명한다
- 2SameSite=Strict/Lax/None 의 동작을 안다
- 3CSRF 토큰 발급·검증 미들웨어를 작성한다
- 4세션과 JWT의 장단점을 비교한다
- 5httpOnly/secure/sameSite/maxAge 4종 세트를 적용한다
1. CSRF란
인증된 사용자를 속여 의도하지 않은 요청을 서버에 보내게 하는 공격.
text
1. 피해자가 bank.com 로그인 (세션 쿠키)
2. 공격자가 evil.com에 악성 폼 심어둠:
<form action="https://bank.com/transfer" method="POST">
<input name="to" value="공격자계좌">
<input name="amount" value="1000000">
</form>
<script>document.forms[0].submit();</script>
3. 피해자가 evil.com 방문
4. 브라우저가 bank.com에 자동으로 쿠키 포함하여 POST
5. bank.com은 유효한 세션으로 판단 → 송금 실행!2. SameSite 쿠키 (현대적 방어)
javascript
// Strict: 같은 사이트에서의 요청에만
res.cookie('sessionId', token, {
httpOnly: true,
secure: true,
sameSite: 'Strict',
});
// Lax: GET은 허용, POST는 차단 (권장)
res.cookie('sessionId', token, { sameSite: 'Lax' });
// None: 크로스사이트 허용 (Secure 필수)
res.cookie('sessionId', token, { sameSite: 'None', secure: true });| 값 | 동작 |
|---|---|
| Strict | 외부 사이트 링크로 온 GET도 쿠키 없음 (가장 엄격) |
| Lax | 외부 GET은 허용, POST 등은 차단 (권장) |
| None | 모두 허용 (Secure 필수) |
3. CSRF 토큰
javascript
// 1. 토큰 생성 (서버)
const crypto = require('crypto');
const csrfToken = crypto.randomBytes(32).toString('hex');
req.session.csrfToken = csrfToken;
res.render('form', { csrfToken });html
<form method="POST" action="/transfer">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input name="amount" type="number">
<button type="submit">송금</button>
</form>javascript
function csrfProtect(req, res, next) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const submitted = req.body._csrf || req.headers['x-csrf-token'];
const stored = req.session.csrfToken;
if (!submitted || submitted !== stored) {
return res.status(403).json({ error: 'CSRF 토큰 검증 실패' });
}
}
next();
}4. 세션 vs JWT
세션
text
[클라이언트] ── POST /login ──> [서버]
세션 생성 { userId: 1 }
<── Set-Cookie: sid=abc ── 저장소(Redis/DB)에 저장
[클라이언트] ── GET /profile ──> [서버] (Cookie: sid=abc)
저장소에서 세션 조회 → userId 확인장점: 즉시 무효화 가능 / 단점: 저장소 필요, 수평 확장 시 세션 공유
JWT
text
[클라이언트] ── POST /login ──> [서버]
JWT 생성 { userId: 1, exp }
<── JWT 토큰 ────── (서버에 저장 안 함)
[클라이언트] ── GET /profile ──> [서버] (Authorization: Bearer eyJ...)
JWT 서명 검증 → userId 확인장점: 저장소 불필요, 수평 확장 / 단점: 만료 전 무효화 어려움
5. httpOnly / Secure 쿠키
javascript
res.cookie('token', value, {
httpOnly: true, // JS에서 document.cookie 접근 불가 → XSS로 쿠키 탈취 방지
secure: true, // HTTPS에서만 전송 → 도청 방지
sameSite: 'Lax', // CSRF 방어
maxAge: 7 * 24 * 60 * 60 * 1000, // 7일
path: '/',
});| 속성 | 목적 |
|---|---|
| httpOnly | XSS를 통한 쿠키 탈취 방지 |
| secure | 네트워크 도청 방지 |
| sameSite | CSRF 방지 |
| maxAge | 세션 하이재킹 최소화 (짧을수록 안전) |