JWT 인증 메커니즘
TL;DR
- 항목 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, 서명 검증, 인증, 인가, 토큰 기반 인증
- 서명 Secret Key로 서명 Private Key로 서명
- 원문 전체는 아래 상세 내용에 그대로 포함했다.
1. 개념
항목 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, 서명 검증, 인증, 인가, 토큰 기반 인증
2. 배경
키 하나의 Secret Key Private Key + Public Key
3. 이유
서명 Secret Key로 서명 Private Key로 서명
4. 특징
검증 같은 Secret Key로 검증 Public Key로 검증
5. 상세 내용
JWT 인증 메커니즘
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, 서명 검증, 인증, 인가, 토큰 기반 인증