지식
Security
세션 관리의 핵심 원칙
웹 세션의 생명 주기와 보안 원칙을 설명합니다. 세션 ID 생성, 쿠키 보안 속성(HttpOnly·Secure·SameSite), 세션 타임아웃, 로그아웃 처리, Redis 기반 세션 저장소 구현까지 다룹니다.
지난 글에서 로그인 자체를 보호하는 방법을 알아봤다. 로그인에 성공한 이후에는 **세션(Session)**이 사용자를 식별하는 수단이 된다. 세션이 탈취되면 로그인 과정 전체가 무의미해진다.
세션 ID 생성 원칙
세션 ID는 반드시 **암호학적으로 안전한 의사 난수 생성기(CSPRNG)**로 생성해야 한다.
import secrets
# 좋은 예: secrets 모듈 사용 (CSPRNG)
session_id = secrets.token_hex(32) # 256비트 = 64자리 16진수
# 나쁜 예: random 모듈 (예측 가능) — 절대 금지
import random
bad_id = str(random.randint(0, 999999))
쿠키 보안 속성
response.set_cookie(
key="session_id",
value=session_id,
httponly=True, # JavaScript 접근 차단 (XSS 완화)
secure=True, # HTTPS에서만 전송 (중간자 공격 방지)
samesite="strict", # CSRF 방어
max_age=1800, # 30분 후 만료
path="/",
)
Redis 기반 세션 저장소
import redis.asyncio as redis
import json, secrets, time
class SessionStore:
def __init__(self):
self.redis = redis.Redis()
self.timeout = 1800
async def create(self, user_id: str) -> str:
session_id = secrets.token_hex(32)
data = {
"user_id": user_id,
"created_at": time.time(),
"last_access": time.time(),
}
await self.redis.setex(
f"session:{session_id}",
self.timeout,
json.dumps(data),
)
return session_id
async def get(self, session_id: str) -> dict | None:
raw = await self.redis.get(f"session:{session_id}")
if raw is None:
return None
await self.redis.expire(f"session:{session_id}", self.timeout)
return json.loads(raw)
async def delete(self, session_id: str):
await self.redis.delete(f"session:{session_id}")
로그아웃 처리
async def logout(session_id: str, response):
await session_store.delete(session_id) # 서버에서 삭제 (필수)
response.delete_cookie("session_id")
response.set_cookie("session_id", "", max_age=0, httponly=True)
로그아웃은 서버와 클라이언트 양쪽에서 처리해야 한다. 서버에서만 삭제하면 다른 기기에서 쿠키가 만료 전까지 남을 수 있다.
지난 글: 무차별 대입 공격 방어 전략
다음 글: 세션 고정(Session Fixation) 공격과 방어
읽어주셔서 감사합니다. 😊