imnyang's workspace

뒤로

JWT란?#

JWT(JSON Web Token)는 서버와 클라이언트 사이에서 정보를 JSON 형태로 전달하는 토큰 형식이에요. 로그인에 성공한 사용자의 식별자나 권한을 토큰에 담고, 서버는 서명을 검증한 뒤 요청을 처리할 수 있어요.

JWT는 인증 방식 자체가 아니라 데이터를 표현하는 형식이에요. 또한 서명된 JWT는 내용을 숨기지 않으므로 비밀번호나 개인정보를 넣으면 안 돼요.

JWT의 구조#

웹에서 자주 사용하는 서명된 JWT는 다음 세 부분을 점(.)으로 연결해요.

Base64URL(Header).Base64URL(Payload).Base64URL(Signature)
text

Header에는 사용할 알고리즘과 토큰 타입을 적어요.

{
  "alg": "HS256",
  "typ": "JWT"
}
json

alg는 서명 알고리즘이고, typ은 토큰 종류예요. Header의 alg를 그대로 믿지 말고 서버가 허용한 알고리즘과 비교해야 해요.

Payload#

Payload에는 Claim이라는 정보를 넣어요.

{
  "iss": "https://auth.example.test",
  "sub": "user-123",
  "aud": "api.example.test",
  "iat": 1788000000,
  "exp": 1788000600,
  "role": "member"
}
json

Payload는 Base64URL로 인코딩했을 뿐이라 누구나 디코딩할 수 있어요.

Signature#

HS256을 예로 들면 Signature는 다음 입력을 비밀키로 서명한 결과예요.

signing_input = Base64URL(Header) + "." + Base64URL(Payload)
signature = HMAC-SHA256(secret, signing_input)
text

Header나 Payload가 한 글자라도 바뀌면 signing_input이 달라져서 기존 Signature와 일치하지 않게 돼요. Signature는 무결성을 확인하지만 Payload를 숨겨주지는 않아요.

주요 Claim#

Claim의미확인할 내용
issIssuer, 발급자예요신뢰하는 인증 서버인지 확인해요
subSubject, 토큰의 대상이에요유효한 사용자나 리소스인지 확인해요
audAudience, 토큰을 사용할 대상이에요현재 API를 대상으로 하는지 확인해요
expExpiration Time, 만료 시각이에요현재 시각이 만료 시각보다 이른지 확인해요
nbfNot Before, 사용 시작 시각이에요너무 이른 사용을 거부해요
iatIssued At, 발급 시각이에요필요하면 발급 시각의 범위를 확인해요
jtiJWT ID, 토큰 식별자예요재사용 탐지나 폐기에 활용해요

exp, nbf, iat는 밀리초가 아니라 초 단위의 NumericDate를 사용해요. 어떤 Claim을 필수로 볼지는 애플리케이션이 정해야 하므로, exp가 없을 때 만료되지 않는 토큰으로 처리하지 않도록 주의해야 해요.

role, scope, tenant_id 같은 Custom Claim도 사용할 수 있어요. 다만 권한 값은 클라이언트 입력이 아니라 서버의 사용자 정보와 정책을 기준으로 발급해야 해요.

JWT 인증 흐름#

사용자 ── 아이디·비밀번호 ──▶ 인증 서버
사용자 ◀──── JWT 발급 ───── 인증 서버
사용자 ── JWT 요청 ────────▶ API 서버
                               │ 서명·Claim 검증

                             응답
text

인증 서버는 로그인에 성공하면 JWT를 발급해요. API 서버는 토큰을 디코딩한 뒤 바로 권한을 결정하지 않고, 서명과 발급자·대상·만료 시간을 모두 확인해야 해요.

JWT는 서버 측 세션처럼 매 요청마다 세션 데이터를 조회하지 않아도 된다는 장점이 있어요. 대신 이미 발급한 토큰을 즉시 회수하기 어렵고, 토큰이 커질수록 모든 요청도 커져요. 로그아웃이나 강제 만료가 중요하다면 서버 측 세션이나 폐기 목록을 함께 사용해야 해요.

안전한 JWT 검증#

검증 과정은 다음 순서로 생각할 수 있어요.

  1. Authorization 헤더의 Bearer 형식과 토큰 크기를 확인해요.
  2. 서버가 허용한 알고리즘과 키를 사용해 Signature를 검증해요.
  3. issaud가 현재 서비스에 맞는지 확인해요.
  4. exp, nbf, iat 같은 시간 Claim을 확인해요.
  5. sub, scope, role을 현재 API의 권한 정책과 비교해요.

서명 검증에 성공했어도 다른 API용 토큰이거나 이미 만료된 토큰이면 거부해야 해요. 특히 Header의 alg를 보고 서버가 임의의 알고리즘을 선택하게 만들면 안 돼요.

JWS와 JWE#

그럼 JWT를 암호화할 순 없을까? 라는 생각이 들어서 JWE라는 것도 찾아보게 되었어요.

  • JWS(JSON Web Signature)는 서명으로 무결성을 보호해요. 흔히 보는 3부분 JWT가 여기에 해당해요.
  • JWE(JSON Web Encryption)는 Payload를 암호화해 내용을 숨겨요. 일반적인 compact 형식은 5부분으로 이루어져요.
Protected Header.Encrypted Key.IV.Ciphertext.Authentication Tag
text

서명은 암호화가 아니므로, 민감하지 않은 정보는 TLS와 JWS로 처리하고 별도의 기밀성이 필요할 때 JWE를 검토하면 돼요.

-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg4c/dn4BitGH1/xNjKoKEp97I2b
eU57QXvkDBEdNNrEMAAAATYmxvZy5pbW55YS5uZy9wb3N0cwAAAAAAAAAGc2hhNTEyAAAA
UwAAAAtzc2gtZWQyNTUxOQAAAEBsTwGRtBNJTPnNR6aU69QptZjtGTVFHtTj6XjhT6autY
ZfPizFiEfgDcdIyyn3HyNR/hNXBXu8p+mVuUx/QgcA
-----END SSH SIGNATURE-----
[Layer7] 2026년 8월 31일 JWT 과제
http://blog.imnya.ng/layer7/23
저자 imnyang
게시일 2026년 09월 01일