본문 바로가기
개념 정리/보안

JWT 1편 - JWT 이해하기

by yyjun 2024. 10. 6.

배경

다른 사람들이 개발한 프로젝트를 보거나, 내가 직접 프로젝트를 개발할 때 로그인 인증/인가 부분을 보면 JWT라는 개념이 자주 나온다. 주로 로그인 성공 시 서버에서 클라이언트에게 JWT 토큰을 발급해 주고, 클라이언트는 요청을 보낼 때 발급받은 JWT 토큰을 함께 전송하여 인증과 인가를 받는 식이다. JWT를 자주 접하기는 했지만 제대로 알고 있지는 않다. 이번에 글을 작성하면서 JWT에 대한 이해를 높이려고 한다.

JWT란?

JWT는 'JSON Web Token'의 약자이다. 말 그대로 JSON 웹 토큰이다.
공식 문서에서는 JWT를 어떻게 정의하는지 살펴보자.

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.
This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

JSON Web Token(JWT)은 당사자 간에 정보를 JSON 객체로 안전하게 전송하는 간결하고 독립적인 방법을 정의하는 개방형 표준(RFC 7519)입니다.
이 정보는 디지털 서명되어 있기 때문에 검증하고 신뢰할 수 있습니다. JWT는 비밀(HMAC 알고리즘 사용) 또는 RSA 또는 ECDSA를 사용하는 공개/비공개 키 쌍을 사용하여 서명할 수 있습니다.
출처: https://jwt.io/introduction
JSON Web Token (JWT)
A string representing a set of claims as a JSON object that is encoded in a JWS or JWE, enabling the claims to be digitally signed or MACed and/or encrypted.

JWT는 JWS 또는 JWE로 인코딩된 JSON 객체로 클레임 세트를 나타내는 문자열이다. 클레임을 디지털 서명하거나 MAC 및/또는 암호화할 수 있습니다.
출처: https://datatracker.ietf.org/doc/html/rfc7519#section-2

 

JWT는 정보를 안전하게 JSON 객체로 전송하는 방법을 정의한 표준이다. 그 표준은 RFC 7519이며 이 링크에서 확인할 수 있다. 안전하게 전송할 수 있는 이유는 JWS 또는 JWE 방식으로 정보를 인코딩하기 때문이다.

 

JWS(JSON Web Signature)와 JWE(JSON Web Encryption)의 가장 큰 차이점은 정보(클레임: 자세한 내용은 후술)의 암호화 여부에 있다. JWS 방식은 클레임을 암호화하지 않는다. 대신 디지털 서명을 사용하여 위변조 여부를 검증한다. JWE 방식은 클레임을 암호화한다. 로그인 기능을 구현할 때는 주로 JWS 방식을 사용하기 때문에 이 글에서는 JWS를 적용한 경우를 주로 다루겠다. JWE 방식을 다루는 곳에서는 따로 말을 적어 놓겠다. 별도의 언급이 없다면 JWS 방식을 사용한 경우라고 이해해주길 바란다.

 

한편, RFC 7519 문서에서는 JWT를 흔히 알고 있는 토큰 문자열로 정의하고 있다. 문맥에 따라서 표준으로 해석되기도 하고, 토큰 문자열로 해석되는 것으로 보인다.

 

다시 본론으로 돌아가보자. 그렇다면 JWT는 왜 사용할까? 위 인용문에서 알 수 있는 것처럼 정보를 JSON 객체로 전송하기 위해 사용한다. 그렇다면 JSON 객체를 HTTP의 바디에 담아서 전송해도 되는데 굳이 JWT를 쓰는 이유는 무엇일까?

 

위 인용문에서 알 수 있는 것처럼 정보를 안전하게 전송하기 위해 사용한다. JWT를 사용하면 정보를 안전하게 전송할 수 있다. 왜냐하면 JWT는 디지털 서명이 되어 있기 때문에 위변조 여부를 검증할 수 있기 때문이다.

또한 정의에 나와 있는 것처럼 JWT는 단순하다. 복잡하지 않은 방법으로 정보를 JSON에 담아 안전하게 전송하고 위변조 여부를 검증할 수 있다.

 

JWT의 정의를 담은 문장 중 "self-contained"라는 단어를 주목할만하다. JWT는 필요한 모든 정보를 담고 있다. 어떤 정보를 담고 있는지는 JWT의 구조를 살펴볼 때 알아보자.

JWT는 언제 사용할까?

공식 문서에 따르면 인가(Authorization)와 정보 교환(Information Exchange) 시 사용할 수 있다고 한다.

두 가지 경우 모두 JWT의 디지털 서명으로 인한 위변조 검증 가능이라는 특성과 연관되어 있다.

 

인가(Authorization)

사용자가 로그인하면 JWT 토큰이 발급된다. 이후 사용자가 서버에 요청을 보낼 때 JWT 토큰을 함께 보낸다. 서버에서는 JWT 토큰을 받아 인증과 인가를 진행한다. JWT의 디지털 서명을 확인해서 이 토큰이 위변조 되었는지 검증할 수 있기 때문에 인가 시 사용할 수 있다. 물론 JWT를 이용한 인증과 인가도 완벽하지는 않다. 보안적인 허점이 존재한다. 그 부분은 별도의 글에서 다루겠다.

 

정보 교환(Information Exchange)

JWT를 이용하여 정보를 주고받으면 디지털 서명 정보를 이용하여 이 정보가 위변조 되지 않았는지를 검증할 수 있다. 그래서 보다 안전하게 정보를 주고받을 수 있고, 정보를 신뢰할 수 있다.

JWT 구조

JWT는 크게 3가지 부분으로 나뉜다.

  • 헤더 (Header)
  • 페이로드 (Payload)
  • 시그니처 (Signature)

각 부분은 점(.)으로 구분된다. 즉, JWT는 다음과 같이 생겼다.

xxxxx.yyyyy.zzzzzz

 

위에서 볼 수 있는 것처럼 '.'으로 문자열이 세 부분으로 나뉘고, 순서대로 헤더, 페이로드, 시그니처라고 한다.

 

JWT의 각 부분에 대해 알아보자.

Header

JWT 헤더는 JSON 형식으로 이루어져 있으며, Base64Url로 인코딩 된다.

일반적으로 두 부분으로 구성된다. 이 토큰의 유형이 JWT라는 사실을 알려주는 부분(예. "typ" : "JWT")과 사용된 서명 알고리즘을 알려주는 부분(예. "alg" : "HS256")으로 구성된다.

 

JWT 헤더 JSON 예시는 다음과 같다. 아래 JSON이 Base64Url로 인코딩 되어 JWT 헤더가 된다.

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

// Base64Url 인코딩 결과:
// ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9

 

요약하면, JWT의 헤더는 이 토큰의 유형이 JWT임을 알려주는 정보와 사용된 서명 알고리즘의 종류를 담고 있고 Base64Url로 인코딩 된다.

Payload

페이로드에는 클레임(claim)이 담겨 있다. 클레임이 뭘까?

Claims are statements about an entity (typically, the user) and additional data.

클레임은 엔터티(일반적으로 사용자)와 추가 데이터에 대한 진술입니다.
출처: https://jwt.io/introduction

 

클레임은 유저와 같은 엔티티와 추가 데이터에 대한 진술이라고 한다. 쉽게 얘기해서, 페이로드에는 전달하고 싶은 정보가 담긴다고 보면 된다. 유저 데이터 같은 정보 말이다. 클레임은 이름과 값의 쌍으로 이루어져 있다.

 

클레임은 등록된(registered) 클레임, 공개(public) 클레임, 비공개(private) 클레임 3가지로 나뉜다.

Registered claims

These are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims.

이는 필수는 아니지만 권장되는 미리 정의된 클레임 세트로, 유용하고 상호 운용 가능한 클레임 세트를 제공합니다.
출처: https://jwt.io/introduction

 

등록된(registered) 클레임은 이름에서도 알 수 있는 것처럼 미리 정의된 클레임 세트이다. 필수적이지는 않지만 사용하는 것이 권장된다. 예를 들어, iss(issuer, 발급자), exp(expiration time, 만료 시간), sub(subject, 주체), aud(audience, 대상) 등의 클레임이 있다. 그렇다면 이러한 클레임은 어디에 등록되어 있는 걸까?

The following Claim Names are registered in the IANA "JSON Web Token Claims" registry established by Section 10.1.

다음 클레임 이름은 섹션 10.1에 의해 설정된 IANA "JSON Web Token Claims" 레지스트리에 등록되어 있습니다.
출처: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1

 

등록된 클레임은 IANA "JSON Web Token Claims" registry에 등록되어 있다. 이 링크로 들어가면 볼 수 있다.

All the names are short because a core goal of JWTs is for the representation to be compact.

모든 이름이 짧은 이유는 JWT의 핵심 목표 중 하나가 간결하게 표현하는 것이기 때문입니다.
출처: https://www.rfc-editor.org/rfc/pdfrfc/rfc7519.txt.pdf 의 4.1

 

등록된 클레임의 예시를 보면 모두 3글자로 이루어져 있는 것을 볼 수 있다. 이는 JWT의 핵심 목표 중 하나가 간결하게 표현하는 것이기 때문에 이름을 짧게 표현했다고 한다. 3글자보다 긴 이름들도 있지만, 줄일 수 있으면 최대한 줄여서 적었다고 한다.

Public claims

Claim Names can be defined at will by those using JWTs. However, in order to prevent collisions, any new Claim Name should either be registered in the IANA "JSON Web Token Claims" registry established by Section 10.1 or be a Public Name: a value that contains a Collision-Resistant Name.

클레임 이름은 JWT를 사용하는 사람이 원하는 대로 정의할 수 있습니다. 그러나 충돌을 방지하기 위해 새로운 클레임 이름은 섹션 10.1에 의해 설정된 IANA "JSON Web Token Claims" 레지스트리에 등록하거나 공개 이름(충돌 방지 이름이 포함된 값)이어야 합니다.
출처: https://datatracker.ietf.org/doc/html/rfc7519#section-4.2

 

공개 클레임의 이름은 JWT를 사용하는 사람이 원하는 대로 정의할 수 있다. 단, 충돌 방지를 위해 새롭게 정의한 이름을 IANA "JSON Web Token Claims" 레지스트리에 등록하거나 새로운 이름에 "Collision-Resistant Name"이 포함되어야만 한다. "Collision-Resistant Name"은 뭘까?

A name in a namespace that enables names to be allocated in a manner such that they are highly unlikely to collide with other names. Examples of collision-resistant namespaces include: Domain Names, Object Identifiers (OIDs) as defined in the ITU-T X.660 and X.670 Recommendation series, and Universally Unique IDentifiers (UUIDs) [RFC4122].

다른 이름과 충돌할 가능성이 매우 낮은 방식으로 이름을 할당할 수 있는 네임스페이스의 이름입니다. 충돌 방지 네임스페이스의 예로는 도메인 이름, ITU-T X.660 및 X.670 권장 사항 시리즈에 정의된 개체 식별자(OID), Universally Unique IDentifiers(UUID)[RFC4122]가 있습니다.
출처: https://datatracker.ietf.org/doc/html/rfc7519#section-2

 

Collision-Resistant Name는 UUID와 같이 충돌 가능성이 매우 낮은 방식으로 이름을 할당할 수 있는 네임스페이스에 속하는 이름을 말한다고 한다. 즉, Collision-Resistant Name은 다른 이름과 겹칠 가능성이 매우 낮은 방식으로 생성된 이름이라고 생각하면 좋을 것 같다. 그렇기 때문에 클레임 이름에 Collison-Resistant Name이 포함되면 충돌을 방지할 수 있는 것이다.

 

이쯤에서 정리할 겸 Registered claim과 Public claim의 차이를 짚고 넘어가 보자.

 

Registered claim은 IANA "JSON Web Token Claims" 레지스트리에 등록된 이름을 가지고 있는 클레임이다.

Public claim은 사용자가 원하는 대로 이름을 정할 수 있다. 등록된 이름을 사용하는 Registered claim과는 다르게 원하는 대로 이름을 정할 수 있다.

(단, Public claim의 이름도 아주 자유롭지는 않다. 원하는 이름을 IANA JSON Web Token Claims 레지스트리에 등록하거나 'Collision-Resistant Name'을 클레임 이름에 포함해야만 한다.)

Private claims

Private claims: These are the custom claims created to share information between parties that agree on using them and are neither registered or public claims.

비공개 클레임: 이는 사용에 동의한 당사자 간에 정보를 공유하기 위해 생성된 사용자 지정 클레임이며 등록 클레임이나 공개 클레임이 아닙니다.
출처: https://jwt.io/introduction
A producer and consumer of a JWT MAY agree to use Claim Names that are Private Names: names that are not Registered Claim Names (Section 4.1) or Public Claim Names (Section 4.2). Unlike Public Claim Names, Private Claim Names are subject to collision and should be used with caution.

JWT의 제작자와 소비자는 비공개 이름인 클레임 이름을 사용하는 데 동의할 수 있습니다. 비공개 이름은 등록된 클레임 이름(섹션 4.1)이나 공개 클레임 이름(섹션 4.2)이 아닌 이름입니다. 공개 클레임 이름과 달리 비공개 클레임 이름은 충돌할 수 있으므로 주의해서 사용해야 합니다.
출처: https://datatracker.ietf.org/doc/html/rfc7519#section-4.3

 

비공개(private) 클레임은 등록된 클레임이나 공개 클레임이 아닌 모든 클레임을 말한다. JWT 생산자와 소비자 모두 비공개 클레임 이름을 사용하는 데 동의하면 사용할 수 있다.

 

앞서 다뤘던 것처럼, 공개 클레임은 충돌 방지를 위해 이름을 IANA JSON Web Token Claims 레지스트리에 등록하거나 클레임 이름에 충돌 방지 이름(Collision-Resistant Name)을 포함시킨다. 하지만 비공개 클레임에는 이러한 이름 충돌 방지책이 없기 때문에 사용 시 충돌할 수 있다. 그렇기에 비공개 클레임을 사용할 때는 주의해서 사용해야 한다.

주의! 페이로드에는 비밀 정보를 넣지 말자

JWT를 사용할 때 페이로드에는 비밀 정보를 넣으면 안 된다. 예를 들어 유저 비밀번호 같은 것이 있다.

 

JWT의 페이로드는 Base64Url 방식으로 인코딩 된다. Base64Url 방식은 암호화 방식이 아니다. Base64Url 방식으로 인코딩 된 문자열을 누구나 디코딩하여 원래 문자열을 알 수 있다. 그렇기 때문에 누구나 JWT 페이로드의 원본 내용을 알 수 있다.

 

JWT 토큰을 보면 이상한 문자들의 나열처럼 보이기 때문에 정보가 암호화되었다고 오해하기 쉽지만, 실제로는 단순히 정보의 형식만 바뀐 것일 뿐 암호화되지 않은 상태이니 조심해야 한다.

(물론 이는 앞에서 다뤘던 것처럼 JWS 방식을 사용한 경우에 해당하는 설명이다. JWE 방식을 사용할 경우 페이로드가 암호화된다.)

 

한편, JWT는 서명되어 있기 때문에 데이터 변조를 방지할 수 있다. 즉, 원하지 않는 누군가가 임의로 토큰 정보를 변경할 수 없다. JWT를 사용하면 데이터 위변조를 방지할 수 있는 것이지 데이터를 암호화할 수 있는 것이 아니다.

Signature

The signature is used to verify the message wasn't changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

시그니처는 메시지가 전송 도중 변경되지 않았는지 확인하는 데 사용되며, 비공개 키로 서명된 토큰의 경우 JWT를 보낸 사람이 주장한 본인인지 확인할 수도 있습니다.
출처: https://jwt.io/introduction

 

JWT의 시그니처는 메시지가 위변조 되었는지 확인하는데 사용한다. 만약 private 키로 서명된 토큰의 경우 JWT를 보낸 사람이 JWT가 주장하고 있는 본인인지도 확인할 수 있다고 한다. 그렇다면 JWT의 시그니처는 어떻게 만들어질까?

To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

시그니처 부분을 만들려면 인코딩된 헤더, 인코딩된 페이로드, 비밀 키, 헤더에 지정된 알고리즘을 가져와서 서명해야 합니다.
출처: https://jwt.io/introduction

 

앞서 JWT의 헤더를 알아볼 때, JWT의 헤더에는 이 JWT 토큰이 어떤 알고리즘을 이용하여 서명되었는지에 대한 정보가 나와 있다고 했다. 시그니처를 만들 때 그 정보를 가져와서 사용한다. 

 

만약에 HMAC SHA256 알고리즘을 사용한다고 해보자. 이때 시그니처는 다음과 같이 만들어진다.

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

 

위 인용 구절에서 말하고 있는 것처럼, 먼저 인코딩된 헤더와 인코딩된 페이로드가 필요하다. 그 두 문자열을 "."을 기준으로 양 옆으로 연결해준다. 연결된 문자열을 헤더에 지정된 알고리즘에 비밀 키와 함께 넣어준다. 그러면 만들고자 했던 JWT의 시그니처 문자열이 나온다.

 

지금까지 살펴본 헤더와 페이로드, 시그니처를 "."으로 연결하면 JWT 토큰이 완성된다.

정리

이번 글에서는 JWT의 정의와 JWT의 사용 이유, JWT의 사용 시점, JWT의 구조에 대해 살펴보았다. 간단하게 이번 글에서 다룬 내용을 정리해보자.

 

  • JWT의 정의
    • JSON Web Token(JWT)은 당사자 간에 정보를 JSON 객체로 안전하게 전송하는 간결하고 독립적인 방법을 정의하는 개방형 표준(RFC 7519)
    • JWT는 JWS 또는 JWE로 인코딩된 JSON 객체로 클레임 세트를 나타내는 문자열
      • 로그인 구현 시 디지털 서명을 이용하는 JWS 방식 페이로드를 암호화 하는 JWE 방식보다 자주 사용됨  
  • JWT를 사용하는 이유
    • 데이터를 안전하게 전송하기 위해서
      • JWS 방식을 사용할 경우 데이터 위변조 검증 가능
  • JWT 사용 시점
    • 인증/인가
    • 정보 교환
  •  JWT 구조
    • 헤더 (Header)
    • 페이로드 (Payload)
    • 시그니처 (Signature)

참고 자료

Introduction to JSON Web Tokens: https://jwt.io/introduction

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

RFC 7519: https://datatracker.ietf.org/doc/html/rfc7519

 

Information on RFC 7519 » RFC Editor

 

www.rfc-editor.org