alltools.one
Security
2025-06-12
8 min
alltools.one Team
JWTSecurityAuthenticationAPIWeb Security

JSON Web Token 보안: 일반적인 취약점과 수정

JWT는 API 인증의 사실상 표준이지만, 겉보기 단순함 뒤에 실제 보안 함정이 숨어 있습니다. 잘못 구성된 JWT는 인증 우회, 권한 상승, 데이터 유출로 이어졌습니다. 이 가이드에서는 가장 중요한 취약점과 예방 방법을 다룹니다.

JWT 구조 요약

JWT는 점으로 구분된 세 개의 Base64URL 인코딩 부분으로 구성됩니다:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature
  • 헤더: 알고리즘과 토큰 유형
  • 페이로드: 클레임 (사용자 데이터, 만료 등)
  • 서명: 암호학적 검증

JWT 인코더/디코더로 JWT 구조를 확인하세요.

JWT 구조에 대한 기초적 이해는 JWT 토큰 설명 가이드를 참조하세요.

치명적 취약점

1. 알고리즘 혼동 공격

가장 위험한 JWT 취약점. 서버가 검증 없이 토큰의 alg 헤더를 수용하면 공격자는:

공격: 알고리즘을 RS256 (비대칭)에서 HS256 (대칭)으로 변경하고 서버의 공개 키로 위조된 토큰에 서명:

// 공격자의 위조 토큰
header: { "alg": "HS256", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
// 서버의 공개 키를 HMAC 비밀로 사용하여 서명

서버가 공개 키를 비밀로 사용하여 HS256 토큰을 검증하면, 위조된 토큰이 검증을 통과합니다.

수정: 항상 예상 알고리즘을 명시적으로 지정:

// 잘못됨 - 토큰이 지정하는 알고리즘을 수용
jwt.verify(token, key);

// 올바름 - 특정 알고리즘 강제
jwt.verify(token, key, { algorithms: ['RS256'] });

2. None 알고리즘 공격

일부 라이브러리는 "alg": "none" — 서명이 없는 토큰을 수용합니다:

// 서명이 없는 위조 토큰
header: { "alg": "none", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
signature: ""  // 비어 있음

수정: 프로덕션에서 none 알고리즘을 절대 허용하지 마세요. 허용된 알고리즘을 명시적으로 화이트리스트에 등록하세요.

3. 약한 서명 비밀

HMAC 기반 JWT (HS256/HS384/HS512)는 비밀만큼만 강합니다:

// 최악 - 초 단위로 무차별 대입 가능
secret = "password123"

// 약함 - 사전 공격에 취약
secret = "my-jwt-secret"

// 강함 - 256+ 비트의 암호학적 무작위
secret = "a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"

수정: HMAC 비밀에 최소 256비트의 암호학적 무작위를 사용하세요. 더 좋은 방법은 서명 키를 공유할 필요가 없는 비대칭 키 (RS256, ES256)를 사용하는 것입니다.

4. 만료 누락

만료 없는 토큰은 영원히 만료되지 않습니다 — 도난된 토큰이 영구적 접근을 부여합니다:

// 잘못됨 - 만료 없음
{ "sub": "user123", "role": "admin" }

// 올바름 - 짧은 만료
{
  "sub": "user123",
  "role": "admin",
  "exp": 1705312800,
  "iat": 1705309200,
  "nbf": 1705309200
}

모범 사례: 액세스 토큰은 15-60분 후 만료. 장기 세션에는 리프레시 토큰 (안전하게 저장)을 사용하세요.

5. 페이로드의 민감한 데이터

JWT 페이로드는 Base64URL 인코딩되어 있으며 암호화되지 않습니다. 누구나 디코딩할 수 있습니다:

echo "eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ" | base64 -d
# {"sub":"123","role":"admin"}

절대 포함하지 마세요: 비밀번호, API 키, 신용카드 번호, 개인 데이터 (SSN, 의료 기록), 또는 JWT 페이로드의 모든 비밀.

6. 토큰 저장 (XSS vs CSRF)

저장소XSS 취약CSRF 취약
localStorage아니오
쿠키 (플래그 없음)
HttpOnly 쿠키아니오
HttpOnly + SameSite 쿠키아니오아니오

권장: JWT를 HttpOnly, Secure, SameSite=Strict 쿠키에 저장하세요. JavaScript 접근을 방지하고 (XSS 방어) 교차 사이트 요청을 방지합니다 (CSRF 방어).

Set-Cookie: token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600

보안 체크리스트

  1. 알고리즘을 명시적으로 지정 — 토큰 헤더에서 수용하지 마세요
  2. none 알고리즘 거부 — 항상 서명을 요구하세요
  3. 강한 비밀 사용 — HMAC에 256+비트, RSA에 2048+비트
  4. 짧은 만료 설정 — 액세스 토큰은 15-60분
  5. 모든 클레임 검증exp, iss, aud, nbf
  6. HttpOnly 쿠키에 저장 — localStorage가 아님
  7. 비밀을 주기적으로 교체 — 키 순환 계획
  8. 페이로드에 민감한 데이터를 저장하지 마세요
  9. 분산 시스템에는 비대칭 키 사용 (RS256 또는 ES256)
  10. 토큰 폐기 구현 — 블랙리스트 또는 짧은 만료 + 리프레시 토큰

비대칭 vs 대칭 서명

측면HS256 (대칭)RS256 (비대칭)
공유 비밀개인/공개 키 쌍
서명 가능비밀을 가진 누구나개인 키 보유자만
검증 가능비밀을 가진 누구나공개 키를 가진 누구나
키 배포비밀을 안전하게 공유공개 키만 공유
성능더 빠름더 느림
적합 용도단일 서비스마이크로서비스, 분산 시스템

권장: 새 애플리케이션에는 ES256 (ECDSA)을 사용하세요 — HMAC에 가까운 성능으로 비대칭 보안을 제공합니다.

토큰 폐기 전략

JWT는 설계상 상태 비저장입니다 — 내장된 폐기 방법이 없습니다. 전략:

  1. 짧은 만료: 토큰이 15분 후 만료되면, 도난된 토큰의 유효 기간이 제한됨
  2. 리프레시 토큰 순환: 사용할 때마다 새 리프레시 토큰 발급; 리프레시 토큰이 두 번 사용되면 모든 토큰 폐기
  3. 블랙리스트: 폐기된 토큰 ID (jti 클레임)를 저장하고 각 요청에서 확인
  4. 토큰 버전 관리: 클레임에 버전 번호를 포함; 로그아웃 시 사용자의 버전 증가

FAQ

인증에 JWT를 사용해야 하나요, 세션 쿠키를 사용해야 하나요?

세션 쿠키는 전통적인 웹 애플리케이션에서 더 간단하고 안전합니다 — 서버가 세션 수명 주기를 제어하고 폐기가 즉시 가능합니다. JWT는 상태 비저장 API, 마이크로서비스, 서버 측 세션 저장이 비실용적인 모바일 앱에 더 적합합니다. JWT를 선택한다면 이 가이드의 보안 조치를 구현하세요.

이상적인 JWT 만료 시간은?

액세스 토큰: 15-60분. 짧을수록 더 안전하지만 더 빈번한 갱신이 필요합니다. 리프레시 토큰: 1-30일, HttpOnly 쿠키에 저장. 일회용 토큰 (이메일 확인, 비밀번호 재설정): 1-24시간.

관련 리소스

Published on 2025-06-12
JSON Web Token Security: Common Vulnerabilities and Fixes | alltools.one