JWT 보안 모범 사례|alg none 공격 / 만료 / 서명 검증
JWT (JSON Web Token)는 최신 Web API 인증의 사실상의 표준이지만, 명세를 잘못 이해한 채 구현하면 쉽게 취약점을 만들 수 있습니다. 이 글에서는 실제로 많이 보이는 JWT 오용 패턴과 이를 피하기 위한 모범 사례를 설명합니다.
JWT 구조 복습
JWT는 3개의 Base64URL 인코딩된 문자열을 점으로 연결한 형식입니다.
eyJhbGci... . eyJzdWIi... . SflKxwRJSM...
header payload signature
- header: 서명 알고리즘(
alg)과 토큰 종류(typ) - payload: 클레임(sub / iss / aud / exp / iat / ...)의 JSON 객체
- signature: header와 payload를 대상으로 한 HMAC / RSA / ECDSA 서명
DevLab의 JWT 디코더로 JWT의 내용을 확인할 수 있습니다. Base64URL은 인코딩이지 암호화가 아니므로, 페이로드의 내용은 서버에서 서명 검증 없이도 누구나 읽을 수 있다는 것을 잊지 마세요.
위협 1: alg=none 공격
JWT 명세에는 "alg": "none"이라는 "서명 없음" 알고리즘 지정이 있습니다. 공격자가 이를 악용하여 {"alg":"none"} 헤더로 페이로드를 수정하고 라이브러리가 그대로 받아들이면 임의의 사용자로 사칭할 수 있는 치명적인 취약점이 됩니다.
대책:
- 검증 시 허용할 알고리즘을 명시적으로 화이트리스트 지정
jwt.verify(token, secret, { algorithms: ['HS256'] })처럼 배열로 전달하기- 2015년경의 라이브러리는 취약했지만 현재의 주요 라이브러리는 대책이 마련되어 있습니다. 다만 직접 구현하지 않는 한 최신 버전을 사용해야 합니다.
// ✗ 悪い例 (アルゴリズム未指定 = ライブラリが alg ヘッダを信頼)
jwt.verify(token, secret);
// ✓ 良い例 (アルゴリズムを固定)
jwt.verify(token, secret, { algorithms: ['HS256'] });
위협 2: 키 혼동 공격 (key confusion)
RSA 공개 키를 가진 공격자가 알고리즘을 RS256에서 HS256으로 변경하여 공개 키를 "비밀 키"로 처리하게 하는 공격입니다. JWT 라이브러리가 alg를 신뢰하고 검증 함수를 선택하면 공개 키로 HMAC 서명된 위조 토큰이 통과합니다.
대책: 알고리즘은 반드시 검증 측에서 고정하세요(위협 1과 동일한 대책). 또한 키 유형도 명시적으로 구분하세요:
- HS256인 경우
Buffer로 전달 - RS256의 경우 PEM 형식의 공개 키를 전달합니다.
위협 3: 유효기간 미검증 / 무기한 토큰
JWT의 exp 클레임은 UNIX 초 단위의 만료 시간을 나타내지만, 검증 중에 무시되는 경우가 많습니다. 한 번 발급된 토큰을 무기한 사용할 수 있다면 유출 시 피해를 막을 수 없습니다.
대책:
- 발급 시
exp를 짧게 설정 (액세스 토큰은 15분~1시간이 기준) - 검증할 때 반드시
exp확인 (주요 라이브러리는 자동으로 수행) - 장기 세션이 필요한 경우 리프레시 토큰 패턴 사용 (단기 액세스 토큰 + 장기 리프레시 토큰 + 서버 측 취소 목록)
위협 4: JWT에 민감 정보를 포함
JWT의 페이로드는 단지 Base64 인코딩일 뿐이며 누구나 디코딩할 수 있습니다. 이를 모르고 페이로드에 암호나 신용 카드 번호를 넣는 구현이 계속 나타나고 있습니다.
대책:
- payload에는 「이 사용자이다」라는 최소한의 식별 정보만 포함 (sub / user_id / role)
- 민감한 정보는 서버 측 데이터베이스에 저장하고 JWT에서 user_id로 조회
- 반드시 JWT로 민감한 정보를 전송해야 하는 경우 JWE (암호화된 JWT)를 사용하세요
위협 5: 실효 (logout)가 불가능
JWT를 무상태로 발급하면 취소할 수 없습니다. 사용자가 로그아웃해도 서버에 토큰 정보가 없으므로 "만료될 때까지 사용할 수 있습니다". 비밀번호를 변경해도 발급된 JWT는 계속 유효합니다.
대책:
- 짧은
exp(15분 이하)로 피해 윈도우 최소화 - 로그아웃 시 서버 측의 실효 목록 (denylist)에
jti(JWT ID)를 등록하고 검증 시 대조 - 중대 이벤트(비밀번호 변경, 권한 변경) 시에는
token_version을 사용자 레코드에 저장하고 검증 시 일치 여부 확인
위협 6: 약한 시크릿
HS256 시크릿이 너무 짧으면 무차별 대입 공격으로 해독될 수 있습니다. 특히 "secret" "password123" 같은 문자열은 즉각적으로 위험합니다.
대책:
- HS256은 최소한 256비트 (32바이트)의 무작위 값을 사용해야 함
openssl rand -base64 32또는 비밀번호 생성 도구로 생성- 시크릿은 절대 Git에 커밋하지 마세요. 환경변수·Secrets Manager로 관리
모범 사례 요약
- 검증 측에서 알고리즘 고정 (
algorithms: ['HS256']) - HS256 시크릿은 256비트 이상, RS256은 2048비트 이상의 RSA
- 액세스 토큰의
exp는 15분~1시간 - 리프레시 토큰 패턴으로 장기 세션 구현
- payload에 기밀 정보를 포함하지 않음 (Base64는 암호화가 아님)
- 로그아웃 시 실효 목록에
jti등록 - 시크릿은 환경변수·Secrets Manager로 관리, 커밋 금지
- 클라이언트 측에서 JWT를 localStorage가 아닌 HttpOnly Cookie에 저장 (XSS 대책)
디버깅에 유용한 도구
- JWT 디코더: header / payload / signature를 시각화하여 각 claim의 의미를 설명과 함께 표시
- JWT 생성・서명 도구: HS256 / HS384 / HS512로 Web Crypto API를 사용하여 브라우저 내에서 서명합니다. 테스트용 토큰 생성에 편리합니다
- 비밀번호 생성 도구: 시크릿 생성에 사용할 수 있습니다
요약
JWT는 올바르게 사용하면 편리한 인증 토큰이지만, 명세의 함정과 공격 패턴을 모르면 운영 시스템에 취약점을 심을 위험이 있습니다. 이 글에서 제시한 6가지 위협과 대책을 파악하고 JWT 라이브러리의 최신 버전을 사용하는 것이 기본입니다. 정기적으로 구현을 검토하고 보안 공지를 추적할 것을 권장합니다.