JWT 가이드

JWT 서명 검증 실패를 제가 디버깅하는 방법

invalid signature 발생 시 원본 token, algorithm allowlist, issuer, kid, JWKS, JWK compatibility, key rotation과 cache를 순서대로 확인하는 실무 흐름입니다.

작성: JWTDecoder.tools마지막 확인 2026-09-13

편집 문체 안내: 1인칭 표현은 JWTDecoder.tools의 문서화된 검증 워크플로를 뜻하며, 게시자와 조직 저자는 JWTDecoder.tools입니다.

JWT 서명 검증 실패 디버깅 흐름
알고리즘, issuer, kid, 키 선택을 확인한 뒤 검증 설정을 변경합니다.

invalid signature를 보면 저는 가장 먼저 원본 JWT가 verification 단계까지 그대로 전달됐는지 확인합니다. JWS signature는 인코딩된 header와 payload의 정확한 bytes를 대상으로 계산되기 때문에 공백 하나를 JSON 안에 다시 넣거나 payload를 디코딩했다가 재인코딩해도 원본 signing input과 달라질 수 있습니다.

제가 사용하는 원칙은 간단합니다.

원본 compact JWT를 보존하고, algorithm → issuer → key source → kid → key compatibility → signature 순서로 확인합니다.

1. 원본 token을 수정하지 않습니다

검증 입력은 개념적으로 다음입니다.

BASE64URL(header) + "." + BASE64URL(payload)

따라서 디버깅용으로 payload를 pretty-print한 뒤 다시 인코딩한 token은 동일한 JSON 의미를 가져도 signature가 달라집니다.

저는 network에서 실제 전달된 raw token을 기준으로 확인합니다. 단, production credential 전체를 장기 로그에 남기지는 않습니다.

2. alg를 verifier allowlist와 비교합니다

header의 alg가 무엇인지 JWT Decoder에서 확인합니다.

예:

{
  "alg": "RS256",
  "kid": "key-2026-09"
}

하지만 alg가 RS256이라고 적혀 있다는 이유로 verifier가 자동으로 RS256을 허용하게 만들지 않습니다. 서버 설정의 allowed algorithms가 먼저입니다.

예상 정책이:

allowed = ["RS256"]

인데 token이 HS256이나 none이면 signature 계산 전에 거부하는 것이 정상입니다.

3. 올바른 issuer의 key를 보고 있는지 확인합니다

invalid signature가 실제로는 잘못된 issuer의 JWKS를 사용한 문제인 경우가 있습니다.

저는:

  1. payload의 iss를 읽고
  2. 애플리케이션 expected issuer와 exact match인지 확인하고
  3. 그 issuer에 연결된 trusted JWKS를 사용합니다.

다른 tenant/environment의 JWKS를 사용하면 kid 이름이 우연히 비슷해도 signature는 맞지 않습니다.

4. kid는 key 선택자일 뿐입니다

JWKS에 token의 kid와 같은 key가 있는지 봅니다. 하지만 여기서 “key found = verified”라고 결론 내리지 않습니다.

JWKS Validator에서 저는 다음 조건을 함께 봅니다.

  • kid exact match
  • kty와 algorithm family compatibility
  • JWK alg가 있다면 일치하는가
  • use가 있다면 sig인가
  • key_ops가 있다면 verify를 포함하는가
  • EC key라면 curve가 맞는가

후보 key가 여러 개 남으면 첫 번째를 임의 선택하지 않고 ambiguity를 오류로 처리합니다.

5. Key material이 실제로 같은지 봅니다

같은 kid 문자열을 재사용하면서 key material이 바뀐 시스템도 있습니다. 이런 운영은 cache와 rotation을 어렵게 만듭니다.

JWK Inspector에서 public JWK thumbprint를 비교하면 환경별 key가 정말 같은지 확인하는 데 도움이 됩니다.

thumbprint가 같다고 trust가 자동으로 생기는 것은 아니지만 key identity를 비교하기에는 유용합니다.

6. HS256이라면 secret encoding까지 확인합니다

HS256 invalid signature에서는 “같은 secret 문자열”이라고 생각했지만 실제 bytes가 다른 경우가 있습니다.

제가 확인하는 항목:

  • raw text secret인지 Base64-decoded bytes인지
  • 환경 변수에 newline/space가 포함됐는지
  • secret rotation 후 old/new 값이 섞였는지
  • client-side에 production secret을 노출하고 있지 않은지

JWT Signature Verifier를 테스트할 때도 synthetic secret으로 재현합니다.

7. RSA/EC key format 문제를 분리합니다

PEM public key를 직접 쓰는 verifier에서는 certificate와 public key 형식을 혼동할 수 있습니다.

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

와 certificate bundle은 의미가 다를 수 있습니다. 라이브러리가 기대하는 key format을 확인합니다.

JWKS를 사용하면 JWK import 단계에서 kty, n/e, crv/x/y 같은 parameter가 올바른지도 확인합니다.

8. Rotation 직후라면 cache를 확인합니다

다음 패턴은 key rotation을 강하게 의심하게 합니다.

old kid token → 성공
new kid token → invalid signature 또는 unknown kid

제가 보는 것:

  • new public JWK가 서명 시작 전에 publish됐는가
  • CDN/JWKS cache TTL이 너무 긴가
  • unknown kid가 오면 verifier가 refresh하는가
  • old/new key가 충분히 overlap됐는가
  • emergency revocation인지 planned rollover인지

자세한 내용은 JWT Key Rotation 가이드에서 다룹니다.

9. Signature encoding을 직접 구현하지 않습니다

JWS algorithm마다 signature 표현 규칙이 다릅니다. ECDSA는 특히 DER signature와 JWS raw R || S 표현 차이 때문에 직접 구현하면 실수하기 쉽습니다.

저는 production verification에서는 검증된 JOSE/JWT library 또는 Web Crypto에 맞는 명확한 변환 코드를 사용합니다. “RSA는 됐으니 EC도 비슷하겠지”라고 가정하지 않습니다.

10. Signature가 맞아도 JWT 전체가 valid한 것은 아닙니다

signature verification이 성공한 뒤에도 다음을 봅니다.

  • iss
  • aud
  • exp
  • nbf
  • token type
  • scope/role

즉:

signature valid
≠
application accepted

입니다.

제가 재현할 때 만드는 테스트 케이스

서명 문제를 고칠 때 저는 최소 다음 케이스를 둡니다.

correct key + correct token → pass
wrong key → fail
modified payload → fail
wrong allowed alg → fail
unknown kid → fail
stale JWKS → refresh 후 pass/fail 명확히

이 테스트를 자동화하면 rotation이나 dependency update 후 regression을 잡기 쉽습니다.

제가 피하는 임시 해결책

  • signature verification을 끄기
  • 모든 algorithm 허용하기
  • unknown kid일 때 아무 key나 시도하기
  • token의 jku URL을 무조건 따라가기
  • issuer validation 없이 전역 JWKS pool 사용하기
  • invalid signature를 warning만 남기고 통과시키기

이런 수정은 디버깅을 끝내는 것이 아니라 trust boundary를 제거하는 것입니다.

제 invalid signature 체크리스트

  1. raw compact JWT가 원본 그대로인가?
  2. alg가 명시적 allowlist에 있는가?
  3. expected issuer가 맞는가?
  4. trusted JWKS/key source를 쓰는가?
  5. kid로 올바른 candidate를 찾았는가?
  6. kty, alg, use, key_ops, curve가 호환되는가?
  7. HS secret 또는 public key bytes가 실제로 맞는가?
  8. rotation/cache가 stale하지 않은가?
  9. 라이브러리 key/signature format이 맞는가?
  10. signature 성공 후 claim validation도 별도로 하는가?

제가 invalid signature를 해결할 때 목표는 단순히 에러를 없애는 것이 아닙니다. 어떤 issuer를 신뢰하고, 어떤 key를 선택했으며, 그 key로 실제 signing input이 검증됐다는 trust chain을 다시 명확하게 만드는 것입니다.

출처 및 참고자료

관련 가이드