JWT 가이드

JWK와 JWKS를 제가 구분해서 보는 방법

JWK는 하나의 JSON Web Key이고 JWKS는 여러 JWK를 담는 key set입니다. issuer, jwks_uri, kid, key selection, signature verification이 실제로 어떻게 연결되는지 설명합니다.

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

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

신뢰 issuer, JWKS, kid, JWK 관계도
신뢰할 수 있는 issuer의 JWKS 안에서 kid가 JWK를 선택하고 그 키로 서명을 검증합니다.

JWT signature 문제를 디버깅할 때 JWK와 JWKS를 같은 뜻처럼 쓰면 key 선택 단계가 금방 헷갈립니다. 저는 둘을 아주 단순하게 구분합니다.

JWK는 하나의 key를 표현하는 JSON object이고, JWKS(JSON Web Key Set)는 여러 JWK를 keys 배열에 담은 문서입니다.

이 구분을 이해하면 issuer → jwks_uri → kid → JWK → signature verify 흐름이 훨씬 명확해집니다.

JWK는 무엇인가

JWK(JSON Web Key)는 암호화 key와 관련 metadata를 JSON으로 표현하는 형식입니다.

RSA public JWK는 예를 들어 다음처럼 보일 수 있습니다.

{
  "kty": "RSA",
  "kid": "2026-09-rsa-1",
  "use": "sig",
  "alg": "RS256",
  "n": "...",
  "e": "AQAB"
}

제가 먼저 보는 필드는 다음과 같습니다.

  • kty: key type (RSA, EC, OKP, oct 등)
  • kid: key identifier
  • alg: 이 key가 어떤 algorithm과 연결되어 있다고 표시되는지
  • use: 보통 sig 또는 enc
  • key_ops: verify, sign 같은 허용 operation
  • RSA의 n, e
  • EC의 crv, x, y

이 metadata는 key를 설명하지만 trust를 자동으로 만들어 주지 않습니다. 저는 항상 “이 JWK를 어디에서 얻었는가?”를 먼저 봅니다.

JWKS는 무엇인가

JWKS는 JWK 여러 개를 담는 문서입니다.

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "old-key",
      "use": "sig",
      "alg": "RS256",
      "n": "...",
      "e": "AQAB"
    },
    {
      "kty": "RSA",
      "kid": "new-key",
      "use": "sig",
      "alg": "RS256",
      "n": "...",
      "e": "AQAB"
    }
  ]
}

여러 key가 필요한 이유 중 하나가 rotation입니다. old token과 new token이 일정 시간 동시에 유효할 수 있기 때문에 JWKS가 old/new public key를 함께 제공할 수 있습니다.

제가 JWT와 JWKS를 연결하는 순서

실제 검증에서 저는 다음 순서를 따릅니다.

1. trusted issuer를 정한다
2. issuer metadata에서 jwks_uri를 얻는다
3. trusted jwks_uri에서 JWKS를 가져온다
4. JWT header의 kid를 확인한다
5. JWKS keys[]에서 후보 JWK를 찾는다
6. alg/kty/use/key_ops/crv 호환성을 확인한다
7. 선택한 public JWK로 signature를 검증한다
8. iss/aud/exp/nbf 등 claim policy를 검증한다

이 순서에서 중요한 점은 kid가 trust의 시작점이 아니라는 것입니다. trust의 시작점은 애플리케이션이 미리 신뢰하는 issuer와 그 issuer에 연결된 key source입니다.

kid는 정확히 무슨 역할인가

kid는 여러 key 중 하나를 선택하는 식별자입니다. key 자체의 hash나 보안 증명이라고 가정하지 않습니다.

예를 들어 token header가:

{
  "alg": "RS256",
  "kid": "new-key"
}

라고 하면 verifier는 trusted JWKS에서 kid=new-key인 후보를 찾을 수 있습니다.

하지만 제가 추가로 확인하는 것이 있습니다.

  • 같은 kid가 여러 개 있는가?
  • kty가 algorithm과 맞는가?
  • use가 있다면 sig인가?
  • key_ops가 있다면 verify를 포함하는가?
  • alg가 있다면 token과 허용 policy에 맞는가?
  • EC라면 curve가 맞는가?

이 과정을 JWKS Validator가 자동으로 도와줍니다.

JWK가 하나뿐이면 kid가 필요 없는가

기술적으로 JWKS에 후보 key가 하나이고 다른 조건으로 명확히 선택할 수 있다면 kid 없이도 검증할 수 있는 구현이 있습니다. 하지만 운영 중 rotation을 고려하면 kid는 매우 유용합니다.

저는 key가 하나뿐인 오늘보다 두 개가 되는 rotation 시점을 생각해서 설계합니다.

JWK와 certificate는 같은 것인가

아닙니다. JWK는 key material과 metadata를 JSON으로 표현한 것입니다. x5c, x5t 같은 certificate 관련 parameter가 포함될 수 있지만 JWK 자체가 X.509 certificate와 동일한 개념은 아닙니다.

JWT 검증에서는 제가 실제로 신뢰해야 하는 것이 issuer의 key distribution 구조인지, certificate chain인지 시스템 설계에 따라 다릅니다.

RFC 7638 thumbprint는 무엇에 쓰는가

JWK thumbprint는 JWK의 필수 public members를 canonical JSON 형태로 만든 뒤 hash하여 key를 안정적으로 식별하는 방법입니다.

JWK Inspector에서 SHA-256 thumbprint를 계산할 수 있습니다.

제가 thumbprint를 쓰는 용도는 주로:

  • 서로 다른 환경에서 같은 public key인지 비교
  • rotation 중 key identity 확인
  • metadata가 달라도 핵심 key material이 같은지 확인

등입니다.

하지만 thumbprint도 trust source를 대신하지 않습니다. 공격자가 제공한 key의 thumbprint를 계산해도 그 key가 갑자기 신뢰할 수 있게 되는 것은 아닙니다.

jku를 자동으로 따라가면 안 되는 이유

JWT header에는 jku 같은 key URL parameter가 있을 수 있습니다. 저는 token이 제공한 URL을 그대로 따라가서 key를 가져오지 않습니다.

그 이유는 trust boundary가 뒤집히기 때문입니다.

나쁜 흐름:
JWT가 jku를 알려줌 → verifier가 그대로 fetch → 그 key로 JWT 검증

이렇게 되면 token이 자기 자신을 검증할 key source를 선택하게 됩니다.

제가 원하는 흐름은:

애플리케이션이 trusted issuer를 앎
→ trusted metadata/JWKS 위치를 결정
→ token header는 그 안에서 key 선택에만 사용

RFC 8725 관점에서도 key URL과 key selection은 엄격한 trust policy가 필요합니다.

OIDC/OAuth metadata와 JWKS

OIDC 또는 OAuth authorization server는 metadata 문서에서 jwks_uri를 제공할 수 있습니다.

예:

{
  "issuer": "https://id.example.com/",
  "jwks_uri": "https://id.example.com/.well-known/jwks.json"
}

제가 이 흐름을 검사할 때는 OIDC Discovery Checker에서 returned issuer가 expected issuer와 정확히 일치하는지, jwks_uri가 HTTPS인지, 필요한 endpoint metadata가 있는지 확인합니다.

Key rotation에서 JWKS가 중요한 이유

rotation을 안전하게 운영할 때 저는 보통 다음 순서를 기대합니다.

  1. new public JWK를 JWKS에 먼저 publish합니다.
  2. cache가 새 JWKS를 볼 시간을 줍니다.
  3. issuer가 new private key로 token 서명을 시작합니다.
  4. old token이 살아 있는 동안 old public JWK도 유지합니다.
  5. 최대 token lifetime과 cache를 고려한 뒤 old key를 제거합니다.

이렇게 하면 unknown kid 장애를 줄일 수 있습니다. 자세한 흐름은 JWT Key Rotation 가이드에서 다룹니다.

제가 보는 흔한 JWKS 장애

kid가 JWKS에 없음

rotation 순서가 잘못됐거나 cache가 오래됐거나 다른 issuer의 JWKS를 보고 있을 수 있습니다.

kid는 있지만 signature 실패

같은 kid 문자열만 보고 안심하지 않습니다. 실제 key material, algorithm, issuer, token 원본 bytes를 다시 확인합니다.

여러 candidate가 남음

ambiguous key selection은 명확한 오류로 처리하는 편이 안전합니다. 첫 번째 key를 무조건 선택하지 않습니다.

JWKS endpoint는 맞지만 issuer가 다름

key가 cryptographically 맞아도 trust policy가 틀릴 수 있습니다. issuer와 key source의 binding을 함께 확인합니다.

JWK와 JWKS를 제가 한 문장으로 기억하는 방법

  • JWK = 하나의 key 표현
  • JWKS = 여러 JWK를 담은 key set
  • kid = trusted set 안에서 key를 고르는 힌트
  • signature verification = 실제 암호학적 확인
  • issuer/audience validation = 애플리케이션 trust 정책 확인

JWT 검증이 실패할 때 저는 kid가 맞다에서 멈추지 않습니다. 어떤 issuer를 신뢰했고, 그 issuer의 어떤 JWKS를 사용했고, 어떤 JWK가 선택됐으며, 실제 signature가 검증됐는지를 순서대로 확인합니다. 이 구조를 유지하면 JWK/JWKS 문제와 claim 문제를 섞지 않고 빠르게 원인을 찾을 수 있습니다.

출처 및 참고자료

관련 가이드