-
로그인/로그아웃 인증 개선이커머스 devops 2026. 1. 7. 14:38
쿠키 VS 세션
쿠키 (Cookie) 세션 (Session) 저장 위치 클라이언트(브라우저) 서버(메모리/DB/Redis) 역할 작은 데이터(4KB)를 브라우저 저장 사용자 상태 정보(복잡한 객체 가능) 서버 저장 크기 4KB 제한 제한 없음 예시 "사용자ID=123", "언어=ko", "테마=dark" 로그인 정보, 장바구니, 권한 보안 낮음 (클라이언트 조작 가능) 높음 (서버만 접근) 속도 빠름 (네트워크 불필요) 느림 (서버 조회 필요) 유효기간 직접 설정 (영구 가능) 짧음 (보통 30분) 용량 제한적 (도메인당 50개) 제한 없음 관계 세션 ID를 쿠키로 전달함 세션
- 세션은 쿠키를 기반으로 하고 있지만 서버 측에서 관리
- 서버에서는 클라이언트를 구분하기 위해 세션ID 부여하여 웹 브라우저가 서버에 접속하여 브라우저를 종료할 때까지 인증상태 유지
- 쿠키보다 보안에는 좋지만 사용자에 대한 정보를 서버에 저장하기 때문에 사용자가 많아질수록 서버 메모리를 많이 사용 (동접자 증가, 서버 과부하로 성능 저하 요인)
- 세션 동작 방식
- 클라이언트가 서버 접속 시 세션 ID 발급
- 클라이언트는 세션ID에 대해 쿠키를 사용해 저장
- 클라이언트는 서버에 다시 접속 시 쿠키를 사용해 세션 ID를 서버에 전달
세션은 쿠키를 "기반"으로 한다
세션 = 집 (서버에 있음) 쿠키 = 주소 (클라이언트가 들고 다님)
집(세션)은 서버에 있지만, 주소(쿠키)가 없으면 집을 찾을 수 없음
→ 세션은 쿠키 없이는 작동 불가 → "쿠키를 기반으로 한다"는 의미
“왜 "기반"이라는 표현을 쓰나?”
세션 자체 = 서버에 저장 (Redis/DB/메모리) 세션 식별 = 쿠키로 전달 (JSESSIONID)
쿠키 보안 속성 전체
[ 4가지 핵심 속성 ] Set-Cookie: JSESSIONID=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1800속성 설정 위치 역할 예시 HttpOnly 서버 XSS 방어 (JavaScript 접근 차단) HttpOnly Secure 서버 HTTPS 강제 (MITM 방어) Secure SameSite 서버 CSRF 방어 SameSite=Lax Path 서버 쿠키 사용 경로 제한 Path=/api Domain 서버 쿠키 사용 도메인 제한 Domain=.shoppy.com Max-Age 서버 쿠키 유효 시간 (초) Max-Age=1800 (30분) Expires 서버 쿠키 만료 시간 Expires=Wed, 07 Jan 2026 12:00:00 GMT XSS vs CSRF vs MITM
XSS (Cross-Site Scripting): └─ 악성 스크립트 주입 └─ document.cookie로 쿠키 탈취 └─ 방어: HttpOnly CSRF (Cross-Site Request Forgery): └─ 사용자 몰래 요청 전송 └─ 쿠키 자동 전송 악용 └─ 방어: SameSite MITM (Man-In-The-Middle): └─ 네트워크 도청 └─ HTTP 쿠키 가로채기 └─ 방어: Secure (HTTPS)기존 레디스 세션 기반 로그인 흐름
[1단계] 로그인 요청 ───────────────────── 브라우저 → 서버 POST /api/login { "email": "user@example.com", "password": "1234" } [2단계] 서버에서 세션 생성 ───────────────────── 서버 (Spring): 1. 인증 성공 확인 2. Redis에 세션 생성: Redis: ┌──────────────────────────────┐ │ key: "spring:session:abc123" │ │ value: { │ │ userId: 100, │ │ username: "홍길동", │ │ roles: ["USER"], │ │ loginTime: "2025-01-07" │ │ } │ └──────────────────────────────┘ 3. 세션 ID 생성: "abc123" [3단계] 세션 ID를 쿠키로 전달 ───────────────────── 서버 → 브라우저 HTTP/1.1 200 OK Set-Cookie: JSESSIONID=abc123; Path=/; HttpOnly; Secure └────┬───┘ └──┬──┘ 세션ID 쿠키명 브라우저: ┌──────────────────────┐ │ 쿠키 저장소 │ ├──────────────────────┤ │ JSESSIONID=abc123 │ ← 저장됨 └──────────────────────┘ [4단계] 이후 모든 요청 ───────────────────── 브라우저 → 서버 GET /api/cart Cookie: JSESSIONID=abc123 ← 자동으로 전송 서버: 1. 쿠키에서 세션 ID 추출: "abc123" 2. Redis에서 세션 조회: GET spring:session:abc123 3. 세션 데이터 확인: { userId: 100, username: "홍길동", ... } 4. "아, 이 사람은 홍길동이구나" 5. 요청 처리SameSite=None; Secure 해도 보안 약한 이유(기존 로그인 방식)
SameSite=None; Secure: - Secure: HTTPS 강제 → 도청 방지 - SameSite=None: 모든 곳에서 쿠키 전송 → CSRF 취약 → 반드시 CSRF 토큰 추가 필요CSRF 공격과 방어
CSRF (Cross-Site Request Forgery):
└─ 사용자가 의도하지 않은 요청을 보내게 만드는 공격
└─ 쿠키가 자동으로 전송되는 특성을 악용
공격 시나리오
[피해자 상태] ────────────── 1. shoppy.com 로그인 완료 2. 브라우저 쿠키 저장소: JSESSIONID=abc123 [공격 과정] ────────────── 3. 피해자가 evil.com 방문 4. evil.com 페이지: <form action="https://shoppy.com/api/cart" method="POST"> <input name="productId" value="999"> <input name="quantity" value="100"> </form> <script> document.forms[0].submit(); // 자동 제출 </script> 5. 브라우저: POST https://shoppy.com/api/cart Cookie: JSESSIONID=abc123 ← 자동으로 쿠키 포함 productId=999&quantity=100 6. shoppy.com 서버: "세션 쿠키 있네? 정상 사용자구나!" → 장바구니에 상품 추가 (사용자는 전혀 모름)- 공격이 가능한 이유 :- 브라우저가 자동으로 쿠키를 전송하기 때문- 서버는 진짜 사용자가 보낸 건지 구분 못함CSRF 방어 방법
1. CSRF 토큰 (가장 강력)
2. SameSite 쿠키 (기본 방어)
3. Custom Header
1. CSRF 토큰 작동 방식: ────────────── 1. 서버: "여기 토큰이야" → _csrf=xyz789 2. 클라이언트: 폼에 토큰 포함 <input type="hidden" name="_csrf" value="xyz789"> 3. 요청 시 토큰 함께 전송 POST /api/cart _csrf=xyz789 productId=1 4. 서버: "토큰 일치? OK!" evil.com: → 토큰을 모르니까 공격 실패! 2. SameSite 쿠키 SameSite=Lax 설정: → 다른 사이트에서 온 요청에는 쿠키 안 보냄! → CSRF 공격 대부분 차단! 3. 3. Custom Header X-Requested-With: XMLHttpRequest evil.com은 커스텀 헤더를 추가할 수 없음 → CORS 정책 때문 → CSRF 공격 차단SameSite
└─ HTTP 쿠키 보안 기능
└─ 브라우저가 쿠키를 언제 전송할지 제어
└─ CSRF 공격 방지 목적
└─ Set-Cookie: JSESSIONID=abc123; SameSite=Lax
1. Strict 동작: └─ 같은 사이트 내에서만 쿠키 전송 └─ 다른 사이트에서 링크 클릭해도 쿠키 안 보냄! 예시: ────────────── 1. shoppy.com 로그인 완료 2. google.com에서 "shoppy.com 바로가기" 클릭 3. shoppy.com 접속 → 쿠키 없음 (로그인 안 된 것처럼 보임) 2. Lax 동작: └─ 상위 레벨 탐색(링크 클릭)만 쿠키 전송 └─ iframe, 이미지, POST 요청은 쿠키 안 보냄 예시: ────────────── 허용: - google.com에서 "shoppy.com" 링크 클릭 (GET) → 쿠키 전송됨 (로그인 유지) 차단: - evil.com의 <form> POST 요청 → 쿠키 안 보냄 (CSRF 차단) - evil.com의 <img src="shoppy.com/api/..."> → 쿠키 안 보냄 3. None 동작: └─ 모든 요청에 쿠키 전송 └─ 반드시 Secure 속성 필요 (HTTPS만) 예시: ────────────── 허용: - 모든 크로스 사이트 요청 - iframe 내 요청 - 서드파티 쿠키JWT vs 세션 방식
1. JWT + localStorage
┌──────────────────┐ │ 브라우저 │ │ ┌────────────┐ │ │ │localStorage│ │ │ │ │ │ │ │ token: │ │ │ │ eyJhbGc... │ │ │ └────────────┘ │ └──────────────────┘ │ │ Authorization: Bearer eyJhbGc... ↓ ┌─────────────────┐ │ Spring Boot │ │ │ │ 1. JWT 추출 │ │ 2. JWT 검증 │ │ 3. 사용자 확인 │ └─────────────────┘ 특징: - 쿠키 사용 안 함 - CSRF 안전 - Stateless (서버 부하 적음) - 모바일 앱 호환 - XSS 취약 (localStorage) - 토큰 탈취 시 무효화 어려움2. JWT + HttpOnly 쿠키
┌─────────────────┐ │ 브라우저 │ │ ┌───────────┐ │ │ │ HttpOnly │ │ │ │ 쿠키 │ │ │ │ │ │ │ │ token= │ │ │ │ eyJhbGc...│ │ │ └───────────┘ │ └─────────────────┘ │ │ Cookie: token=eyJhbGc... (자동) ↓ ┌─────────────────┐ │ Spring Boot │ │ │ │ 1. 쿠키에서 JWT │ │ 2. JWT 검증 │ │ 3. 사용자 확인 │ └─────────────────┘ 특징: - XSS 방어 (HttpOnly) - Stateless - 자동 전송 - CSRF 취약 (SameSite 필요) - 쿠키 설정 복잡3. 세션 + 쿠키 (현재 방식)
┌─────────────────┐ │ 브라우저 │ │ ┌───────────┐ │ │ │ 쿠키 │ │ │ │ JSESSIONID│ │ │ │ =abc123 │ │ │ └───────────┘ │ └─────────────────┘ │ │ Cookie: JSESSIONID=abc123 ↓ ┌─────────────────┐ │ Spring Boot │ │ 1. 세션 ID 추출 │ └─────────────────┘ │ ↓ ┌─────────────────┐ │ Redis │ │ abc123 → { │ │ userId: 100 │ │ username... │ │ } │ └─────────────────┘ 특징: - XSS 방어 (HttpOnly) - 즉시 로그아웃 가능 - Spring 기본 지원 - CSRF 취약 - Stateful (확장성 제한) - Redis 부하현재 프로젝트 개선 방안
[ 현재 상황 ] - REST API 스타일 - CSRF 비활성화 - 세션/쿠키 사용 중 [ 문제점 ] - CSRF 공격 취약 [ 개선 방안 ] 1. JWT + localStorage + CSRF 비활성화 2. 세션 + 쿠키 + CSRF 활성화 3. 세션 + 쿠키 + SameSite=Lax [ 최고 보안 조합 ] JWT + HttpOnly 쿠키 + SameSite + CSRF 토큰 1️⃣ JWT를 HttpOnly 쿠키에 저장 └─ XSS 방어 2️⃣ SameSite=Lax 설정 └─ CSRF 기본 방어 3️⃣ CSRF 토큰 추가 └─ CSRF 완벽 방어 4️⃣ Secure 속성 └─ HTTPS 강제 (MITM 방어) 5️⃣ Refresh Token 분리 └─ Access Token 탈취 시 피해 최소화- 세션 + 쿠키 + CSRF 활성화가 제일 간단하게 개선 가능
- Redis 성능 개선을 위해 JWT + Redis으로 개선하는 것도 좋아보임
(세션 + Redis) VS (JWT + Redis)
세션 + redis JWT + redis 인증 방식 세션 ID JWT 토큰 Access Token 세션 ID (Redis에 저장) JWT (클라이언트에 저장) Refresh Token 없음 Redis에 저장 서버 부하 매번 Redis 조회 Access Token은 조회 없음 로그아웃 세션 삭제 Blacklist 추가 Stateless Stateful Stateless 확장성 제한적 우수 토큰 VS 쿠키
무엇 역할 Token 인증 정보 (데이터) 사용자 식별 Cookie 저장/전송 방법 (메커니즘) 브라우저 저장 + 자동 전송 관계 - 토큰을 쿠키에 담을 수 있음
- 토큰을 localStorage에 담을 수도 있음
- 쿠키는 토큰의 운반 수단 중 하나토큰의 종류
형태 특징 저장 위치 JWT (JSON Web Token) eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 - 3부분: Header.Payload.Signature
- 자체 포함 (Self-contained)
- 서버 검증만으로 사용자 확인
- 서버에 저장 안 함- 쿠키 (권장)
- localStorage
- sessionStorageSession ID abc-123-def-456-ghi-789 - 랜덤 문자열
- 실제 데이터는 서버에 저장
- ID만으로는 의미 없음
- 서버 조회 필수쿠키 (거의 항상) API Key sk_live_51H6K8LG2eC3dybfY... - 고정된 키
- 서버 간 통신
- 사용자별 발급- 환경 변수
- 헤더에 직접 전송OAuth Access Token ya29.a0AfH6SMBx... - OAuth 2.0 표준
- 소셜 로그인
- 짧은 수명- localStorage
- 쿠키728x90'이커머스 devops' 카테고리의 다른 글
현재 Spring Security 구조 (0) 2026.01.07 동시성문제 - 비관적 락 (0) 2026.01.03 스프링부트게시판 (4) (0) 2025.11.29 스프링부트 게시판 (3) (0) 2025.11.28 스프링부트 게시판 (2) (0) 2025.11.27