JWT 인증 메커니즘
TL;DR
- JWT 인증 메커니즘의 핵심 개념과 용어를 한눈에 정리한다.
- JWT 인증 메커니즘이(가) 등장한 배경과 필요성을 요약한다.
- JWT 인증 메커니즘의 특징과 적용 포인트를 빠르게 확인한다.
1. 개념
JWT 인증 메커니즘은(는) 핵심 용어와 정의를 정리한 주제로, 개발/운영 맥락에서 무엇을 의미하는지 설명한다.
2. 배경
기존 방식의 한계나 현업의 요구사항을 해결하기 위해 이 개념이 등장했다는 흐름을 이해하는 데 목적이 있다.
3. 이유
도입 이유는 보통 유지보수성, 성능, 안정성, 보안, 협업 효율 같은 실무 문제를 해결하기 위함이다.
4. 특징
- 핵심 정의와 범위를 명확히 한다.
- 실무 적용 시 선택 기준과 비교 포인트를 제공한다.
- 예시 중심으로 빠른 이해를 돕는다.
5. 상세 내용
작성일: 2026-02-24 카테고리: Backend / Security / Authentication 포함 내용: JWT, JSON Web Token, Header, Payload, Signature, HMAC, Access Token, Refresh Token, Session 비교, Stateless, 서명 검증, Base64, Claims, Bearer Token
1. JWT의 구조
┌─────────────────────────────────────────────────────────────┐
│ JWT = 3개의 파트 │
├─────────────────────────────────────────────────────────────┤
│ │
│ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 │
│ .eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6ImFkbWluIn0 │
│ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Header │ │ Payload │ │ Signature │ │
│ │ (Base64) │. │ (Base64) │. │ (서명값) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Header │
├─────────────────────────────────────────────────────────────┤
│ │
│ { │
│ "alg": "HS256", // 서명 알고리즘 │
│ "typ": "JWT" // 토큰 타입 │
│ } │
│ │
│ → Base64URL 인코딩 (암호화 아님! 누구나 디코딩 가능) │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Payload (Claims) │
├─────────────────────────────────────────────────────────────┤
│ │
│ { │
│ "sub": "user123", // Subject (사용자 ID) │
│ "role": "admin", // 사용자 역할 │
│ "iat": 1706857200, // Issued At (발급 시간) │
│ "exp": 1706860800 // Expiration (만료 시간) │
│ } │
│ │
│ → Base64URL 인코딩 (암호화 아님! 누구나 디코딩 가능) │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Signature │
├─────────────────────────────────────────────────────────────┤
│ │
│ HMAC-SHA256( │
│ Base64(Header) + "." + Base64(Payload), │
│ SECRET_KEY │
│ ) │
│ │
│ → SECRET KEY를 아는 서버만 생성/검증 가능 │
│ → 무결성 보장: 내용이 바뀌면 서명이 달라짐 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 핵심 포인트 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Header, Payload = Base64 인코딩만 → 누구나 볼 수 있음 │
│ Signature = SECRET KEY 필요 → 위조 불가능 │
│ │
│ 여권 비유: │
│ ┌───────────────────────────────────────────────────┐ │
│ │ JWT = 여권 │ │
│ │ Payload = 여권에 적힌 이름/국적 (누구나 볼 수 있음)│ │
│ │ Signature = 정부 직인 (위조 불가) │ │
│ │ SECRET KEY= 원본 도장 (정부만 보유) │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2. 인증 흐름 상세
┌─────────────────────────────────────────────────────────────┐
│ Step 1: 로그인 요청 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Client Server │
│ │ │ │
│ │ POST /login │ │
│ │ { "id": "user123", │ │
│ │ "pw": "password" } │ │
│ │ ────────────────────────────► │ │
│ │ │ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 2: 서버에서 JWT 생성 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Header 생성 → Base64URL 인코딩 │
│ { "alg": "HS256", "typ": "JWT" } │
│ → eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 │
│ │
│ 2. Payload 생성 → Base64URL 인코딩 │
│ { "sub": "user123", "role": "admin", "exp": ... } │
│ → eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6ImFkbWluIn0 │
│ │
│ 3. Signature 생성 (SECRET KEY 사용) │
│ HMAC-SHA256(header + "." + payload, SECRET_KEY) │
│ → SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │
│ │
│ 4. 세 파트를 점(.)으로 연결 │
│ header.payload.signature │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 3: JWT를 클라이언트에 전달 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Server Client │
│ │ │ │
│ │ HTTP 200 OK │ │
│ │ { │ │
│ │ "accessToken": "eyJ...", │ │
│ │ "refreshToken": "eyJ..." │ │
│ │ } │ │
│ │ ────────────────────────────► │ │
│ │ │ │
│ │ │ 저장: │
│ │ │ - 브라우저 메모리 │
│ │ │ - httpOnly Cookie │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 4: 이후 모든 API 요청 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Client Server │
│ │ │ │
│ │ GET /api/profile │ │
│ │ Authorization: Bearer eyJ... │ │
│ │ ────────────────────────────► │ │
│ │ │ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 5: 서버의 JWT 검증 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. JWT를 점(.)으로 분리 │
│ header / payload / signature │
│ │
│ 2. 같은 SECRET KEY로 서명 재계산 │
│ expected = HMAC-SHA256(header+"."+payload, SECRET_KEY) │
│ │
│ 3. 서명 비교 │
│ expected == received_signature? → 통과 or 거절 │
│ │
│ 4. exp, iat 확인 (만료 여부) │
│ │
│ 5. Payload에서 사용자 정보 추출 │
│ sub, role 등 → 요청 처리에 활용 │
│ │
└─────────────────────────────────────────────────────────────┘
3. 서명 검증의 원리
┌─────────────────────────────────────────────────────────────┐
│ 왜 위조가 불가능한가? │
├─────────────────────────────────────────────────────────────┤
│ │
│ 공격자 시나리오: │
│ │
│ 원본 JWT: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ payload: { "sub": "user123", "role": "user" } │ │
│ │ signature: abc123 (서버가 SECRET KEY로 생성) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 공격자가 Payload 변조 시도: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ payload: { "sub": "user123", "role": "admin" } ← 변조│ │
│ │ signature: abc123 (그대로 유지) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 서버 검증: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 서버가 변조된 payload로 서명 재계산 │ │
│ │ → HMAC(변조된 payload, SECRET_KEY) = xyz789 │ │
│ │ → xyz789 ≠ abc123 → 위조 탐지! 요청 거절 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 결론: SECRET KEY 없이는 올바른 Signature를 만들 수 없음 │
│ │
└─────────────────────────────────────────────────────────────┘
4. Session 방식과의 비교
┌─────────────────────────────────────────────────────────────┐
│ Session 방식 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Client Server DB / Redis │
│ │ │ │ │
│ │ 로그인 요청 │ │ │
│ │ ────────────► │ │ │
│ │ │ 세션 저장 │ │
│ │ │ ───────────────► │ │
│ │ sessionId │ │ │
│ │ ◄──────────── │ │ │
│ │ │ │ │
│ │ API 요청 │ │ │
│ │ Cookie: sid │ │ │
│ │ ────────────► │ │ │
│ │ │ 세션 조회 (매 요청마다) │
│ │ │ ───────────────► │ │
│ │ │ ◄─────────────── │ │
│ │ │ │ │
│ 특징: 세션은 서버/DB에 저장, 모든 요청마다 DB 조회 필요 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ JWT 방식 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Client Server │
│ │ │ │
│ │ 로그인 요청 │ │
│ │ ────────────► │ │
│ │ JWT 발급 │ (서버에는 아무것도 저장 안 함) │
│ │ ◄──────────── │ │
│ │ │ │
│ │ API 요청 │ │
│ │ Bearer JWT │ │
│ │ ────────────► │ │
│ │ │ 서명만 검증 (DB 조회 없음) │
│ │ 응답 │ │
│ │ ◄──────────── │ │
│ │
│ 특징: 서버에 아무것도 저장 안 함, JWT에 모든 정보 포함 │
│ │
└─────────────────────────────────────────────────────────────┘
5. JWT가 Session보다 나은 점
┌─────────────────────────────────────────────────────────────┐
│ 장점 1. Stateless │
├─────────────────────────────────────────────────────────────┤
│ │
│ Session: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Server Memory: { "sid-abc": {userId: 1, role: ...}}│ │
│ │ → 서버 재시작하면 모든 세션 소멸 │ │
│ │ → Redis 없으면 다중 서버에서 세션 공유 불가 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ JWT: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Server Memory: (비어 있음) │ │
│ │ → 서버 재시작해도 토큰은 유효 │ │
│ │ → 메모리/Redis 불필요 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 장점 2. 수평 확장 (Scale-out) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Session 방식의 문제: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 사용자A ─────► Server1 (세션 있음) ✓ │ │
│ │ 사용자A ─────► Server2 (세션 없음) ✗ 로그아웃! │ │
│ │ → Sticky Session 또는 공유 Redis 필요 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ JWT 방식: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 사용자A ─────► Server1 (SECRET KEY로 검증) ✓ │ │
│ │ 사용자A ─────► Server2 (SECRET KEY로 검증) ✓ │ │
│ │ → 어느 서버든 SECRET KEY만 있으면 검증 가능 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 장점 3. 마이크로서비스 인증 전파 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Session 방식: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Client → Service A → Service B → Service C │ │
│ │ ↓ ↓ ↓ │ │
│ │ Redis Redis Redis │ │
│ │ → 모든 서비스가 공유 세션 저장소에 의존 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ JWT 방식: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Client → Service A → Service B → Service C │ │
│ │ Bearer JWT Bearer JWT Bearer JWT │ │
│ │ → 토큰을 그대로 전달, 각 서비스가 독립적으로 검증 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 장점 4. Cross-domain / Mobile 친화적 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Session/Cookie 방식의 제약: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ - 쿠키는 동일 도메인에서만 자동 전송 │ │
│ │ - api.example.com ↔ app.example.com: 주의 필요 │ │
│ │ - 모바일 앱에서 쿠키 처리 복잡 │ │
│ │ - CORS 설정 까다로움 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ JWT 방식의 자유로움: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ - Authorization 헤더: 도메인 무관 어디서나 작동 │ │
│ │ - iOS, Android, Web 모두 동일하게 처리 │ │
│ │ - CORS 문제 없음 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
6. JWT의 단점
┌─────────────────────────────────────────────────────────────┐
│ 단점 1. 즉시 무효화 불가 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Session: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "이 세션 삭제" → 즉시 로그아웃 완료 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ JWT: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 토큰 탈취됐어도... 만료 시간까지는 유효 │ │
│ │ 서버는 "무효화"할 방법이 없음 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 완화 방법: │
│ ├── Blacklist: 무효화할 토큰 ID를 DB에 저장 │
│ ├── 짧은 만료 시간: 15분~1시간 │
│ └── Refresh Token 패턴: Refresh Token만 DB에서 관리 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 단점 2. 토큰 크기 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Session ID: 수십 바이트 (예: "abc123def456...") │
│ JWT: 수백~수천 바이트 (Header+Payload+Signature) │
│ │
│ → 매 API 요청마다 헤더에 포함 → 네트워크 오버헤드 │
│ → Claims이 많아질수록 크기 증가 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 단점 3. Payload 노출 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Base64 디코딩은 누구나 가능: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ eyJzdWIiOiJ1c2VyMTIzIn0 │ │
│ │ → base64 decode │ │
│ │ → { "sub": "user123", "role": "admin" } (노출!) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 주의사항: │
│ ├── 절대로 비밀번호를 Payload에 넣지 말 것 │
│ ├── 민감한 개인정보는 Payload에 최소화 │
│ └── 암호화가 필요하면 JWE(JSON Web Encryption) 사용 │
│ │
└─────────────────────────────────────────────────────────────┘
7. Access Token + Refresh Token 패턴
┌─────────────────────────────────────────────────────────────┐
│ Access Token vs Refresh Token │
├─────────────────────────────────────────────────────────────┤
│ │
│ Access Token │
│ ├── 수명: 짧음 (15분 ~ 1시간) │
│ ├── 용도: API 요청 시 인증 수단 │
│ └── 특징: 탈취되어도 짧은 시간 내 만료 │
│ │
│ Refresh Token │
│ ├── 수명: 김 (7일 ~ 30일) │
│ ├── 용도: Access Token 재발급에만 사용 │
│ └── 특징: DB에 저장 → 즉시 폐기 가능 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 전체 흐름 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 로그인 │
│ Client → Server: POST /login │
│ Server → Client: accessToken(15분) + refreshToken(7일) │
│ │
│ 2. API 호출 │
│ Client → Server: Authorization: Bearer {accessToken} │
│ │
│ 3. Access Token 만료 │
│ Client → Server: POST /token/refresh │
│ { refreshToken: "eyJ..." } │
│ Server → Client: 새 accessToken 발급 │
│ │
│ 4. Refresh Token 만료 │
│ → 다시 로그인 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 보안상 이점 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Access Token 탈취 시: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ → 최대 15분만 유효 → 피해 최소화 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Refresh Token 탈취 시: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ → DB에서 해당 Refresh Token 즉시 삭제 │ │
│ │ → 더 이상 Access Token 재발급 불가 │ │
│ │ → 피해 즉시 차단 가능 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
8. 서명 알고리즘
┌─────────────────────────────────────────────────────────────┐
│ HS256 - HMAC (대칭 방식) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 하나의 SECRET KEY │ │
│ │ │ │ │
│ │ ┌─────────────┴─────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ 서명(Sign) 검증(Verify) │ │
│ │ (Auth Server) (Auth Server) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 특징: │
│ ├── 단순하고 빠름 │
│ ├── 서명하는 쪽과 검증하는 쪽이 같은 키를 공유 │
│ └── 모놀리식, 단일 서버 환경에 적합 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ RS256 - RSA (비대칭 방식) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Private Key (비공개) Public Key (공개) │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ 서명(Sign) 검증(Verify) │ │
│ │ (Auth Server만) (누구든 Public Key로) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 특징: │
│ ├── Auth Server: Private Key로 서명 │
│ ├── 다른 서비스: Public Key로만 검증 (Secret 공유 불필요) │
│ └── 마이크로서비스, 다중 서비스 환경에 적합 │
│ │
└─────────────────────────────────────────────────────────────┘
| 항목 | HS256 (대칭) | RS256 (비대칭) |
|---|---|---|
| 키 | 하나의 Secret Key | Private Key + Public Key |
| 서명 | Secret Key로 서명 | Private Key로 서명 |
| 검증 | 같은 Secret Key로 검증 | Public Key로 검증 |
| 키 공유 | 검증하는 쪽도 Secret 필요 | Public Key만 배포 (안전) |
| 적합 환경 | 모놀리스, 단일 서버 | 마이크로서비스, 다중 서비스 |
관련 키워드
JWT, JSON Web Token, Access Token, Refresh Token, Bearer Token, HMAC, HS256, RS256, Signature, Claims, Payload, Stateless, Session, Base64, 서명 검증, 인증, 인가, 토큰 기반 인증