쿠키, 세션, JWT 완벽 비교: 인증 방식의 차이와 실무 선택 기준

쿠키, 세션, JWT 완벽 비교: 인증 방식의 차이와 실무 선택 기준

“쿠키와 세션의 차이가 뭔가요?”, “JWT가 뭔가요?”, “세션 대신 JWT를 쓰는 이유는?” — 인증 관련 질문은 신입 면접의 단골이다. HTTP의 무상태(Stateless) 특성에서 시작해서, 쿠키·세션·JWT 각각이 이 한계를 어떻게 극복하는지, 그리고 실무에서 어떤 기준으로 선택하는지를 다룬다.


1. HTTP는 왜 상태를 기억하지 못하는가

1.1 Stateless 프로토콜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────────────────┐
│                    HTTP의 Stateless 특성                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  요청 1: "로그인해주세요" (ID: hong, PW: 1234)                      │
│  서버: "로그인 성공!"                                                │
│                                                                     │
│  요청 2: "내 주문 내역 보여주세요"                                    │
│  서버: "너 누구야?"  ← 이전 요청 기억 못함!                         │
│                                                                     │
│  HTTP는 각 요청을 독립적으로 처리한다.                                │
│  이전 요청의 정보를 기억하지 않는다 (Stateless).                     │
│  → 로그인 상태를 유지하려면 별도 메커니즘이 필요                     │
│                                                                     │
│  해결 방법 3가지:                                                    │
│  ① 쿠키: 클라이언트가 정보를 저장하고 매 요청에 보냄                │
│  ② 세션: 서버가 상태를 저장하고, 클라이언트에 ID만 줌               │
│  ③ JWT: 토큰 자체에 정보를 담아 클라이언트가 보관                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2.1 동작 원리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────────────────┐
│                    쿠키 동작 흐름                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Client                              Server                        │
│    │                                    │                           │
│    │── POST /login ──────────────────→  │                           │
│    │   (ID, PW)                         │ 인증 확인                 │
│    │                                    │                           │
│    │←── Set-Cookie: userId=1 ─────────  │ 쿠키 설정                │
│    │                                    │                           │
│    │── GET /orders ──────────────────→  │                           │
│    │   Cookie: userId=1                 │ 쿠키 자동 전송           │
│    │                                    │ "userId=1이구나"          │
│    │←── 주문 내역 ────────────────────  │                           │
│    │                                    │                           │
│  브라우저가 쿠키를 저장하고,                                         │
│  같은 도메인 요청 시 자동으로 헤더에 포함하여 전송                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2.2 쿠키의 주요 속성

1
2
3
4
5
6
7
Set-Cookie: sessionId=abc123;
            Path=/;
            Domain=example.com;
            Max-Age=3600;
            HttpOnly;
            Secure;
            SameSite=Lax
속성 설명
HttpOnly JavaScript에서 접근 불가 (XSS 방어)
Secure HTTPS에서만 전송
SameSite=Lax 크로스 사이트 요청 시 쿠키 제한 (CSRF 방어)
Max-Age 쿠키 유효 시간 (초). 없으면 브라우저 종료 시 삭제
Domain 쿠키가 전송될 도메인
Path 쿠키가 전송될 경로

2.3 쿠키의 한계

1
2
3
4
5
● 용량 제한: 약 4KB
● 보안 취약: 클라이언트에 저장되므로 변조 가능
  → Cookie: userId=1 → userId=2로 변조하면 다른 사용자 행세 가능!
● 매 요청마다 전송: 불필요한 데이터도 전송 → 네트워크 부담
● 브라우저 전용: 모바일 앱, API 서버 간 통신에는 부적합

3. 세션 기반 인증

3.1 동작 원리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────────────┐
│                    세션 기반 인증 흐름                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Client                              Server                        │
│    │                                    │                           │
│    │── POST /login ──────────────────→  │                           │
│    │   (ID, PW)                         │ ① 인증 확인              │
│    │                                    │ ② 세션 생성              │
│    │                                    │    Session Store:         │
│    │                                    │    {abc123: {userId: 1,  │
│    │                                    │              name: 홍길동}}│
│    │                                    │                           │
│    │←── Set-Cookie: JSESSIONID=abc123── │ ③ 세션 ID를 쿠키로 전달 │
│    │                                    │                           │
│    │── GET /orders ──────────────────→  │                           │
│    │   Cookie: JSESSIONID=abc123        │ ④ 세션 ID로 사용자 조회  │
│    │                                    │    → userId=1, name=홍길동│
│    │←── 주문 내역 ────────────────────  │                           │
│                                                                     │
│  핵심: 실제 데이터는 서버에 저장, 클라이언트는 세션 ID만 보관       │
│  → 쿠키에는 세션 ID(의미 없는 랜덤 문자열)만 담김                  │
│  → ID를 변조해도 서버에 해당 세션이 없으면 인증 실패                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3.2 세션의 장단점

1
2
3
4
5
6
7
8
9
10
장점:
● 서버에서 상태 관리 → 보안성 높음
● 세션 ID는 의미 없는 값 → 변조해도 소용없음
● 서버에서 즉시 무효화 가능 (로그아웃, 강제 만료)
● 세션에 어떤 데이터든 저장 가능

단점:
● 서버 메모리 사용 → 사용자가 많으면 부담
● 서버 확장(Scale-out) 시 세션 공유 문제
● Stateful → 특정 서버에 종속될 수 있음

3.3 서버 확장 시 세션 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────┐
│            Scale-out 시 세션 문제와 해결                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  문제:                                                              │
│  요청1 → Server A (세션 생성: abc123)                               │
│  요청2 → Server B (abc123 세션 없음! → 인증 실패)                  │
│                                                                     │
│  해결 방법:                                                         │
│                                                                     │
│  ① Sticky Session (세션 고정)                                      │
│     로드밸런서가 같은 사용자를 항상 같은 서버로 보냄                │
│     단점: 특정 서버에 트래픽 편중                                   │
│                                                                     │
│  ② Session Clustering                                              │
│     서버 간 세션 데이터 복제                                        │
│     단점: 서버가 많아지면 복제 비용 증가                            │
│                                                                     │
│  ③ 외부 세션 저장소 (Redis) ★ 실무에서 가장 많이 사용              │
│     ┌──────────┐                                                   │
│     │  Redis   │ ←── 모든 서버가 같은 세션 저장소 사용             │
│     └──────────┘                                                   │
│      ▲   ▲   ▲                                                    │
│   Server A B C                                                     │
│                                                                     │
│  ④ JWT (토큰 기반) → 서버에 세션을 저장하지 않음                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4. JWT 기반 인증

4.1 JWT(JSON Web Token) 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────────────┐
│                    JWT 구조                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwibmFtZSI6Iu2Zi...          │
│  ├── Header ──┤├── Payload ──────────────────┤├── Signature ──┤    │
│                                                                     │
│  Header (헤더):                                                     │
│  {                                                                  │
│    "alg": "HS256",    // 서명 알고리즘                              │
│    "typ": "JWT"                                                     │
│  }                                                                  │
│                                                                     │
│  Payload (페이로드 = Claims):                                       │
│  {                                                                  │
│    "sub": "1",           // 사용자 ID                               │
│    "name": "홍길동",      // 사용자 이름                             │
│    "role": "USER",       // 권한                                    │
│    "iat": 1711036800,    // 발급 시간 (issued at)                   │
│    "exp": 1711040400     // 만료 시간 (expiration)                  │
│  }                                                                  │
│                                                                     │
│  Signature (서명):                                                  │
│  HMACSHA256(                                                        │
│    base64UrlEncode(header) + "." + base64UrlEncode(payload),       │
│    secretKey    // 서버만 아는 비밀 키                               │
│  )                                                                  │
│                                                                     │
│  ★ Payload는 Base64 인코딩일 뿐, 암호화가 아님!                    │
│  → 누구나 디코딩해서 내용을 볼 수 있음                              │
│  → 민감한 정보(비밀번호 등)를 넣으면 안 됨!                        │
│  → Signature로 "위변조 여부"만 검증                                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4.2 동작 원리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────────────────────────────────────────────────────────┐
│                    JWT 인증 흐름                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Client                              Server                        │
│    │                                    │                           │
│    │── POST /login ──────────────────→  │                           │
│    │   (ID, PW)                         │ ① 인증 확인              │
│    │                                    │ ② JWT 생성 (서명 포함)   │
│    │                                    │    서버에 저장 X!         │
│    │←── { accessToken: "eyJ..." } ────  │ ③ 토큰 응답              │
│    │                                    │                           │
│    │    클라이언트가 토큰 저장                                      │
│    │    (localStorage, 메모리, 쿠키 등)                             │
│    │                                    │                           │
│    │── GET /orders ──────────────────→  │                           │
│    │   Authorization: Bearer eyJ...     │ ④ 토큰 검증              │
│    │                                    │   - 서명 유효한지        │
│    │                                    │   - 만료되지 않았는지    │
│    │                                    │   - Payload에서 사용자 추출│
│    │←── 주문 내역 ────────────────────  │                           │
│                                                                     │
│  핵심: 서버가 상태를 저장하지 않음 (Stateless)                      │
│  토큰 자체에 사용자 정보가 들어있고, 서명으로 위변조를 검증          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4.3 JWT의 장단점

1
2
3
4
5
6
7
8
9
10
11
장점:
● Stateless → 서버 확장(Scale-out) 용이
● 서버 메모리/DB 조회 불필요 (토큰 자체로 인증)
● 모바일, SPA, MSA 등 다양한 환경에 적합
● 서버 간 인증 정보 공유 쉬움 (같은 secretKey면 어떤 서버든 검증 가능)

단점:
● 토큰 탈취 시 만료 전까지 막을 수 없음 (서버에서 무효화 불가)
● Payload가 커지면 네트워크 부담 (매 요청마다 전송)
● 토큰 자체에 정보가 있어 변경 불가 (사용자 권한 변경 시 기존 토큰 무효)
● 강제 로그아웃이 어려움

5. 쿠키 vs 세션 vs JWT 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────────────┐
│                쿠키 vs 세션 vs JWT                                   │
├──────────────┬────────────┬────────────┬────────────────────────────┤
│              │ 쿠키       │ 세션       │ JWT                        │
├──────────────┼────────────┼────────────┼────────────────────────────┤
│ 저장 위치    │ 클라이언트 │ 서버       │ 클라이언트                 │
│ 상태 관리    │ Stateless  │ Stateful   │ Stateless                  │
│ 보안         │ 낮음       │ 높음       │ 중간                       │
│              │ (변조 가능)│ (서버 관리)│ (탈취 시 위험)             │
│ 서버 부담    │ 없음       │ 있음       │ 없음                       │
│              │            │ (메모리)   │                            │
│ 확장성       │ 좋음       │ 나쁨       │ 좋음                       │
│              │            │ (세션 공유)│                            │
│ 강제 만료    │ 불가       │ 가능       │ 기본적으로 불가            │
│ 용도         │ 설정 저장  │ 전통 웹    │ REST API, SPA, 모바일     │
│              │ 장바구니   │ 서버 렌더링│ MSA                        │
└──────────────┴────────────┴────────────┴────────────────────────────┘

6. Access Token과 Refresh Token

6.1 왜 토큰을 두 개 쓰는가?

1
2
3
4
5
문제: Access Token의 딜레마
● 유효 시간을 길게 → 탈취 시 오래 악용됨
● 유효 시간을 짧게 → 자주 재로그인해야 함 (UX 나쁨)

해결: Access Token(짧은 수명) + Refresh Token(긴 수명)

6.2 동작 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────┐
│              Access Token + Refresh Token 흐름                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ① 로그인                                                          │
│  Client → POST /login → Server                                    │
│  Server → { accessToken (30분), refreshToken (14일) }              │
│                                                                     │
│  ② API 요청 (accessToken 유효)                                     │
│  Client → GET /api/orders (Authorization: Bearer {accessToken})    │
│  Server → 200 OK + 데이터                                         │
│                                                                     │
│  ③ accessToken 만료                                                │
│  Client → GET /api/orders (만료된 accessToken)                     │
│  Server → 401 Unauthorized                                        │
│                                                                     │
│  ④ 토큰 재발급                                                     │
│  Client → POST /auth/refresh (refreshToken 전송)                   │
│  Server → { 새 accessToken (30분) }                                │
│           refreshToken이 유효하면 재발급                            │
│                                                                     │
│  ⑤ refreshToken도 만료되면                                         │
│  → 다시 로그인 필요                                                │
│                                                                     │
│  Access Token:  짧은 수명 (15분~1시간)                              │
│  Refresh Token: 긴 수명 (7일~30일), 서버 DB/Redis에 저장           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

6.3 Refresh Token은 어디에 저장하는가?

1
2
3
4
5
6
7
8
9
10
11
12
Access Token:
  ● 메모리 (변수) — 가장 안전, 새로고침 시 사라짐
  ● localStorage — XSS에 취약
  ● httpOnly 쿠키 — XSS 방어, CSRF 주의

Refresh Token:
  ● httpOnly + Secure 쿠키 (권장)
  ● 서버 DB 또는 Redis에도 저장 (검증 + 강제 만료 위해)

실무 추천:
  Access Token → httpOnly 쿠키 또는 메모리
  Refresh Token → httpOnly + Secure 쿠키 + 서버 Redis에 저장

7. JWT의 장점과 한계

7.1 Stateless의 장점이 명확한 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────┐
│              JWT가 적합한 아키텍처                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  MSA (Microservices Architecture):                                  │
│    API Gateway → User Service                                      │
│               → Order Service                                      │
│               → Payment Service                                    │
│    → 각 서비스가 독립적으로 JWT 검증 가능                           │
│    → 서비스 간 세션 공유 불필요                                     │
│                                                                     │
│  모바일 + 웹 동시 지원:                                             │
│    모바일 앱: 쿠키 사용 어려움 → JWT를 헤더로 전송                 │
│    웹 SPA: API 서버와 분리 → JWT로 인증                            │
│                                                                     │
│  서버리스 / CDN:                                                    │
│    서버가 상태를 가질 수 없는 환경 → JWT 필수                      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

7.2 JWT의 한계

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 강제 로그아웃이 안 됨
   세션: 서버에서 세션 삭제하면 끝
   JWT: 발급된 토큰은 만료 전까지 유효
   → 해결: 블랙리스트 (Redis에 무효화된 토큰 저장)

2. 토큰 탈취 시 위험
   세션: 세션 ID 탈취해도 서버에서 무효화 가능
   JWT: 탈취된 토큰으로 만료 전까지 API 호출 가능
   → 해결: Access Token 수명을 짧게 (15분~30분)

3. Payload 변경 불가
   사용자 권한이 변경되어도 기존 토큰의 Payload는 그대로
   → 해결: 토큰 재발급 또는 짧은 만료 시간

8. 로그아웃/강제 만료 처리

8.1 세션 방식

1
2
3
4
5
6
7
8
9
// 간단! 서버에서 세션 삭제하면 끝
@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session != null) {
        session.invalidate(); // 세션 무효화
    }
    return ResponseEntity.ok().build();
}

8.2 JWT 방식 — 블랙리스트 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
@RequiredArgsConstructor
public class AuthService {

    private final RedisTemplate<String, String> redisTemplate;

    // 로그아웃: 토큰을 블랙리스트에 추가
    public void logout(String accessToken) {
        long expiration = tokenProvider.getExpiration(accessToken);
        long ttl = expiration - System.currentTimeMillis();

        if (ttl > 0) {
            redisTemplate.opsForValue()
                    .set("blacklist:" + accessToken, "logout", Duration.ofMillis(ttl));
        }
    }

    // 토큰 검증 시 블랙리스트 확인
    public boolean isValid(String accessToken) {
        if (redisTemplate.hasKey("blacklist:" + accessToken)) {
            return false; // 블랙리스트에 있으면 무효
        }
        return tokenProvider.validate(accessToken);
    }
}

8.3 Refresh Token Rotation

1
2
3
4
5
6
7
8
보안 강화: Refresh Token을 사용할 때마다 새로 발급

① Client: refreshToken으로 재발급 요청
② Server: 새 accessToken + 새 refreshToken 발급
           기존 refreshToken 무효화
③ 만약 이미 무효화된 refreshToken으로 요청이 오면?
   → 탈취된 것으로 간주
   → 해당 사용자의 모든 Refresh Token 무효화 (강제 재로그인)

9. 신입 면접에서 자주 나오는 비교 포인트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────────────┐
│              면접 핵심 비교 포인트                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  "쿠키와 세션의 차이"                                                │
│  → 쿠키: 클라이언트 저장, 세션: 서버 저장                           │
│  → 세션도 세션 ID를 쿠키에 담아 전송 (쿠키가 운반 수단)            │
│                                                                     │
│  "세션과 JWT의 차이"                                                 │
│  → 세션: Stateful (서버에 상태), JWT: Stateless (토큰에 정보)      │
│  → 세션: 강제 만료 쉬움, JWT: 강제 만료 어려움                     │
│  → 세션: Scale-out 어려움, JWT: Scale-out 쉬움                     │
│                                                                     │
│  "JWT를 쓰는 이유"                                                   │
│  → 서버 확장 시 세션 공유 문제 없음                                 │
│  → 모바일·SPA·MSA 환경에 적합                                      │
│  → 서버 메모리 부담 없음                                            │
│                                                                     │
│  "JWT의 단점"                                                        │
│  → 토큰 탈취 시 만료 전까지 차단 불가                               │
│  → Payload 크기만큼 네트워크 부담                                   │
│  → 강제 로그아웃을 위해 결국 블랙리스트(Redis) 필요                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

면접 예상 질문 & 답변

Q1. 쿠키와 세션의 차이를 설명해주세요.

쿠키는 클라이언트(브라우저)에 데이터를 저장하고, 같은 도메인으로 요청할 때 자동으로 전송됩니다. 약 4KB 제한이 있고, 클라이언트에서 변조할 수 있어 민감한 정보를 직접 저장하기에 부적합합니다.

세션은 서버에 데이터를 저장하고, 클라이언트에는 세션 ID만 쿠키로 전달합니다. 실제 사용자 정보는 서버에 있으므로 보안성이 높지만, 서버 메모리를 사용하고 Scale-out 시 세션 공유 문제가 생깁니다.

핵심 차이는 데이터 저장 위치(클라이언트 vs 서버)와 보안성(낮음 vs 높음)입니다.

Q2. JWT란 무엇이고, 어떻게 동작하나요?

JWT는 JSON Web Token으로, Header·Payload·Signature 세 부분을 .으로 이어붙인 문자열입니다. Payload에 사용자 정보(ID, 권한 등)가 담기고, Signature는 서버의 비밀 키로 서명하여 위변조를 방지합니다.

로그인 시 서버가 JWT를 발급하고, 클라이언트가 이후 요청마다 Authorization: Bearer {토큰} 헤더에 담아 보냅니다. 서버는 Signature를 검증하고 Payload에서 사용자 정보를 추출합니다. 서버에 상태를 저장하지 않으므로 Stateless합니다.

Q3. 세션 대신 JWT를 쓰는 이유는?

가장 큰 이유는 서버 확장성입니다. 세션은 서버에 상태를 저장하므로 Scale-out 시 세션 공유 문제(Sticky Session, Redis 등)를 해결해야 합니다. JWT는 토큰 자체에 정보가 담겨 있어 어떤 서버에서든 검증할 수 있습니다.

또한 모바일 앱, SPA, MSA 환경에서 다양한 클라이언트와 서비스 간 인증을 유연하게 처리할 수 있습니다.

Q4. JWT의 단점은 무엇인가요?

첫째, 토큰이 탈취되면 만료 전까지 차단할 수 없습니다. 서버에 상태가 없으므로 “이 토큰을 무효화해라”가 기본적으로 불가능합니다. 블랙리스트(Redis)를 도입하면 해결되지만, Stateless의 장점이 희석됩니다.

둘째, Payload는 암호화가 아닌 인코딩이므로 누구나 디코딩할 수 있습니다. 민감한 정보를 넣으면 안 됩니다.

셋째, 토큰 크기만큼 매 요청마다 네트워크 부담이 생깁니다.

Q5. Access Token과 Refresh Token은 왜 나누나요?

Access Token의 수명을 짧게 하면 탈취 피해를 줄일 수 있지만, 자주 재로그인해야 합니다. Refresh Token은 Access Token이 만료되었을 때 재로그인 없이 새 Access Token을 발급받기 위한 용도입니다.

Access Token은 15분~1시간으로 짧게, Refresh Token은 7~30일로 길게 설정합니다. Refresh Token은 서버 DB나 Redis에도 저장하여 강제 무효화가 가능하도록 합니다.

Q6. JWT 로그아웃은 어떻게 구현하나요?

JWT는 서버에서 발급한 토큰을 회수할 수 없으므로, 블랙리스트 방식을 사용합니다. 로그아웃 시 해당 Access Token을 Redis에 남은 만료 시간만큼 TTL로 저장합니다. 이후 요청이 오면 블랙리스트에 있는지 먼저 확인하고, 있으면 인증을 거부합니다.

Refresh Token은 서버 DB/Redis에서 삭제하여 재발급을 차단합니다.

Q7. JWT의 Payload는 암호화되나요?

아닙니다. JWT의 Payload는 Base64 인코딩일 뿐 암호화가 아닙니다. 누구나 디코딩해서 내용을 확인할 수 있습니다. 따라서 비밀번호, 개인정보 같은 민감한 데이터를 Payload에 넣으면 안 됩니다.

Signature는 위변조 방지 용도입니다. Payload를 수정하면 Signature가 일치하지 않아 검증에 실패합니다. 내용을 볼 수 있지만 수정은 불가능한 구조입니다.


마무리

HTTP의 Stateless 한계를 극복하는 세 가지 방법을 정리하면:

  • 쿠키: 클라이언트에 직접 데이터 저장, 보안 약함
  • 세션: 서버에 상태 저장 + 세션 ID를 쿠키로 전달, 보안 강함, 확장 어려움
  • JWT: 토큰 자체에 정보, Stateless, 확장 쉬움, 강제 만료 어려움

선택 기준은 명확하다: 전통적인 서버 렌더링 웹이면 세션, REST API + SPA/모바일/MSA면 JWT. 그리고 JWT를 쓴다면 Access Token + Refresh Token 구조는 거의 필수다.