ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로그인/로그아웃 인증 개선
    이커머스 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
    - sessionStorage
    Session 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
Designed by Tistory.