"JWT expired" 및 일반적인 JSON Web Token 오류 해결 방법: 완전 가이드
JSON Web Token (JWT)은 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 업계 표준입니다. 현대적인 웹 및 모바일 애플리케이션의 인증(Authentication) 및 인가(Authorization)에 널리 사용됩니다. 하지만 암호화된 서명이 있고 엄격한 만료 규칙을 따르는 경우가 많아 디버깅 시 큰 어려움을 겪을 수 있습니다.
이 가이드에서는 가장 일반적인 JWT 오류를 분석하고, 발생 원인과 해결 방법을 설명합니다.
1. 일반적인 JWT 오류 메시지
Node.js의 jsonwebtoken과 같은 라이브러리를 사용하는 경우 다음과 같은 일반적인 오류 이름을 접하게 됩니다.
TokenExpiredError: 토큰은 유효하지만 만료 시간이 지났습니다.JsonWebTokenError: invalid signature: 토큰의 서명이 제공된 비밀키/공개키와 일치하지 않습니다.JsonWebTokenError: jwt malformed: 제공된 문자열이 유효한 JWT 구조가 아닙니다.JsonWebTokenError: jwt signature is required: 서명 섹션이 없는 토큰이 제공되었습니다.JsonWebTokenError: jwt invalid audience: 토큰의aud(audience) 클레임이 예상 값과 일치하지 않습니다.
2. 주요 원인 및 해결책
2.1 "TokenExpiredError" (JWT expired)
모든 보안 JWT에는 exp (만료 시간) 클레임이 포함되어야 합니다. 현재 시간이 이 값을 지나면 토큰은 더 이상 유효하지 않습니다.
증상:
사용자가 갑자기 로그아웃되거나, API 요청 시 jwt expired 메시지와 함께 401 Unauthorized 오류가 반환되기 시작합니다.
해결책:
- 리프레시 토큰 (Refresh Tokens): 액세스 토큰이 만료될 때마다 사용자가 수동으로 로그인할 필요가 없도록 "리프레시 토큰" 전략을 구현합니다.
- 시계 동기화: 서버의 시계가 NTP를 사용하여 동기화되어 있는지 확인하세요. 몇 초의 오차만으로도 문제가 발생할 수 있습니다.
iat및nbf확인: "발급 시간"(iat) 및 "활성 시작 시간"(nbf) 클레임도 올바른지 확인합니다.
2.2 "invalid signature" (invalid JWT signature)
JWT는 헤더(Header), 페이로드(Payload), 서명(Signature)의 세 부분으로 구성됩니다. 서명은 헤더와 페이로드를 비밀키로 해싱하여 생성됩니다. 페이로드의 문자 하나만 바뀌거나 잘못된 비밀키를 사용하면 서명이 무효화됩니다.
원인:
- 토큰 검증에 잘못된
secret또는public key를 사용함. - 서명에 사용된 알고리즘(예: HS256)과 검증 알고리즘(예: RS256)이 일치하지 않음.
- 전송 중 토큰이 변조되거나 손상됨.
해결책:
- 비밀키 환경 변수를 다시 한번 확인하세요.
- JWT 헤더의
alg가 검증에 사용하는 알고리즘과 일치하는지 확인하세요.
2.3 "jwt malformed" 또는 "jwt decode failed"
유효한 JWT는 점(.)으로 구분된 세 부분(header.payload.signature)으로 구성되어야 합니다. 어느 한 부분이 누락되거나 문자열이 유효한 Base64URL이 아니면 형식이 잘못된 것으로 간주됩니다.
원인:
- "Bearer " 접두사가 포함된 문자열을 라이브러리에 직접 전달함 (예:
atob("Bearer <token>")). - 토큰의 일부만 복사하여 붙여넣음.
- 토큰 문자열에 공백이나 숨겨진 문자가 포함됨.
해결책:
디코더에 전달하기 전에 반드시 Bearer 접두사를 제거했는지 확인하세요.
const token = authHeader.split(' ')[1]; // "Bearer <token>"에서 토큰 추출
2.4 "jwt invalid audience" 또는 "jwt invalid issuer"
이러한 오류는 페이로드의 aud (audience) 또는 iss (issuer) 클레임이 서버에서 예상하는 값과 일치하지 않을 때 발생합니다.
해결책: 인증 서버(Auth0, Firebase 또는 자체 서버)의 구성이 애플리케이션 코드의 검증 옵션과 일치하는지 확인하세요.
3. 고급 문제 해결
3.1 페이로드 조사
서명 오류가 발생하는 경우 첫 번째 단계는 페이로드를 조사하여 예상한 데이터가 포함되어 있는지 확인하는 것입니다. 페이로드는 단순히 Base64로 인코딩되어 있으므로 디코딩하여 읽는 데 비밀키가 필요하지 않습니다.
3.2 알고리즘 혼동 공격 (Algorithm Confusion Attacks)
라이브러리가 예상하는 특정 알고리즘만 허용하도록 구성되어 있는지 확인하세요. 일부 구형 라이브러리는 공격자가 헤더를 alg: none으로 변경하여 보안을 우회할 수 있는 공격에 취약했습니다. 최신 라이브러리는 대부분 이를 수정했지만, 항상 허용되는 알고리즘을 명시적으로 정의하는 것이 좋습니다.
jwt.verify(token, secret, { algorithms: ['HS256'] });
4. 예방 및 모범 사례
- 짧은 수명의 액세스 토큰: 액세스 토큰의 수명을 짧게 유지하고(예: 15분), 긴 세션에는 리프레시 토큰을 사용합니다.
- 보안 비밀키: JWT 비밀키를 코드에 직접 작성하지 마세요. 환경 변수를 사용하고 정기적으로 교체하세요.
- 모든 요청 검증: 서버 측에서 서명을 검증하지 않은 JWT를 신뢰하지 마세요.
- HTTPS 사용: "중간자 공격"(MITM)을 방지하기 위해 항상 HTTPS를 통해 토큰을 전송하세요.
5. 자주 묻는 질문 (FAQ)
Q: 비밀키 없이 JWT 내부의 데이터를 읽을 수 있나요?
A: 네. 헤더와 페이로드는 Base64URL로 인코딩되어 있을 뿐 암호화되지 않았습니다. 토큰을 가진 사람은 누구나 데이터를 읽을 수 있습니다. 비밀번호나 신용카드 번호와 같은 민감한 정보를 JWT 페이로드에 절대 넣지 마세요.
Q: HS256과 RS256의 차이점은 무엇인가요?
A: HS256은 서명과 검증 모두에 단일 비밀키를 사용합니다(대칭키). RS256은 서명에 비밀키를, 검증에 공개키를 사용합니다(비대칭키). 이는 분산 시스템에서 더 안전합니다.
Q: 프론트엔드에서 "jwt expired" 오류를 어떻게 처리하나요?
A: API 인터셉터에서 401 오류를 잡습니다. 오류가 "jwt expired"인 경우 리프레시 토큰 엔드포인트 호출을 시도합니다. 실패하면 사용자를 로그인 페이지로 리디렉션합니다.
6. 빠른 확인 도구
특정 토큰에 문제가 있나요? 저희의 **JWT 디코더 및 디버거**를 사용해 보세요. 다음이 가능합니다.
- 모든 JWT를 즉시 디코딩하여 헤더와 페이로드 확인.
- 만료 상태 및
iat,exp,nbf등의 클레임 확인. - 잘못된 형식의 토큰 (malformed tokens) 및 인코딩 문제 식별.
- 서명 검증 (로컬에서 비밀키를 제공하는 경우).
관련 오류
- JSON의 'Unexpected token' 오류 해결 방법
- 'invalid base64 string' 오류 수정 방법
- 'invalid regular expression' 오류 해결 방법