본문 바로가기
프로그래밍/기타

[JWT] JWT(Json Web Token)에 대해서 자세히 알아봅시다 - 이론편

by 사바라다 2023. 2. 19.

안녕하세요. 오늘은 JWT와 그 기본적인 스펙에 대해 자세히 알아보는 시간을 가져보도록 하겠습니다.

저의 옛날 포스팅에 보면 JWT에 대해서 간단히 설멍했던 글이 있습니다.

[REST API] REST API의 Security With JWT

하지만 너무 단순한 개념적인 부분만 설명드렸기 때문에 이번 기회로 제대로 정리해보고자 합니다.

JWT 란 무엇인가 ?

JWT는 Json Web Token의 약자로써 서로다른 기기에 데이터를 전달할 때 사용하는 방법중 하나입니다.

구성은 기본적으로 Base64포맷이며 Header와 Body(claim 이라고 부르기도 합니다) 그리고 Signature 부분으로 나뉘어집니다. 실제로 JWT는 아래처럼 생겼습니다. .을 기준으로 slice하면 첫번째 부분부터 Header, Body, Signature 입니다. 각각에 대한 자세한 이야기는 아래에서 진행하도록 하겠습니다.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJLYXJvbCJ9.8Ql4P8CirU_rEbhJKSwimrQ9_7MsMhffycxy02J_h9I

JWT는 header와 body에 사인(Sign)이 가능하고 추가로 암호화(aka. Encrypted JWT)도 가능합니다. 기본적으로 Sign이 들어가기 때문에 header 또는 body를 수정하게 되면 Signature가 맞지 않게 됩니다. 따라서 데이터의 변조 방지가 가능합니다. 그리고 만료기간을 설정할 수 있습니다. 이러한 점 때문에 주로 유저가 로그인 후 정상적인 접근을 증명하는 AccessToken으로 사용할 수 있고 사용합니다.

JWT의 형태와 예시

아래는 JWT의 간단한 형태입니다. 이것은 Base64로 인코딩되어있기 때문에 Readable하진 않습니다. 하지만 암호화와는 다르기 때문에 이를 발급받은 Client가 원한다면 값을 디코딩 해볼 수 있습니다.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJLYXJvbCJ9.8Ql4P8CirU_rEbhJKSwimrQ9_7MsMhffycxy02J_h9I

JWT는 위에서 설명했지만 .를 기준으로 첫번째부터 Header, Body, Signature로 나뉘어집니다. 이를 각각 디코딩하게 되면 Json 포맷을 가지고 있다는 것을 알수 있습니다.

Header

JWT의 Header는 JWT의 metadata들을 나타냅니다. 여기에는 Sign에 사용된 Algorithms, format, 그리고 ContentType 등의 정보가 들어갈 수 있습니다.

위 예제에서 Header는 eyJhbGciOiJIUzI1NiJ9 입니다. 이를 base64 디코딩을 진행하면 아래와 같은 정보를 얻을 수 있습니다. alg는 JWT에 Signed를 진행했을 때인 JWS의 표준 헤더중 하나입니다. RFC-7515문서 보시면 JWT에 사용된 Signiture 알고리즘의 종류라는 것을 알 수 있습니다.

{"alg":"HS256"}

Payload

Payload는 JWT의 Body에 해당되는 부분입니다. Payload의 단위는 Claims로써 사실상 실제 데이터는 Claims라는 단위로 저장됩니다면 json의 필드라고 생각해주시면 될것 같습니다. 이러한 Claims에는 JWT 생성자가 원하는 정보들을 자유롭게 담을 수 있습니다. 이 역시 Json 형식을 가지고 있기 때문에 단일 필드도 가능하지만 Object와 같은 complexible한 필드 역시 추가가 가능합니다. ex) {"user" : {...}}

위 예제에서 Claim은 eyJzdWIiOiJLYXJvbCJ9입니다. 이를 한번 Base64 디코딩을 진행해보겠습니다. iss라는 필드가 있는 것을 확인할 수 있습니다. iss는 JWT의 표준인 RFC-7519에 명시되어있는 Registered Fields 중 하나입니다. 해당 필드의 풀네임은 issuer이고 해당 JWT의 발급자를 나타냅니다. 아래에서 발급자는 Karol 이라는 사실을 Client는 Decoding을 통해서 알 수 있습니다.

{"iss":"Karol"}

Signature

위에서 확인했던 Header와 Claims는 모두 Base64로 인코딩 되어있으며 암호화는 되어있지 않는 것을 알 수 있습니다. 그렇다면 이것은 공격자가 값을 바꿀 수 있다는 뜻입니다. 이 경우 서버에서는 JWT 내부의 값이 변도 되었다는 사실을 어떻게 알 수 있을까요 ? 바로 해당 Signature를 통해서 알 수 있습니다. Signature는 아래와 같은 방식으로 만들어지게 됩니다.

String concatenated = encodedHeader + '.' + encodedClaims
Key key = getMySecretKey()
byte[] signature = hmacSha256( concatenated, key )

Base64로 인코딩한 Header와 Claims를 먼저 만든 후 이를 Key를 통해서 Sign하는 방식입니다. 따라서 Client에서 Header 또는 Claims의 값을 변경한다면 이는 signature의 불일치로 인해서 서버에서 받아들여지지 않습니다.

JWT의 Registered Fields

JWT 스펙인 RFC-7515와 Signed JWT(JWS) 스펙인 RFC-7519를 보면 표준으로 사용할 수 있는 Registered Fields를 나열하고 있습니다. 각각에 대해서 간단히 언급하는 수준으로 살펴보고 가도록 하겠습니다. 해당 필드들은 필수는 아니고 모두 선택적(Optional)입니다. 하지만 커스텀을 하더라도 표준 필드를 알면 더 좋은 JWT 설계를 할 수 있기 때문에 알고있는것은 중요한 덕목중 하나 일 것입니다.

  • iss (Issuer) : JWT를 발급한 주체
  • sub (Subject) : JWT의 발급의 목적(주제)
  • aud (Audience) : JWT 발급받은 수신자
    • 해당 값으로 식별이 가능해야합니다.
  • exp (Expiration Time) : JWT 만료시간
  • nbf (Not Before) : JWT 활성화 시간
  • iat (Issued At) : JWT 발급 시간
  • jti (JWT ID) : JWT 발급의 Unique ID

해당 claims 중 exp, nbf, iat는 모두 NumericDate 타입이며 이는 1636027948와 같이 seconds(milliseconds 아님)로 나타냅니다.

마무리

오늘은 이렇게 JWT에 대해서 자세히 알아보는 시간을 가져보았습니다.

다음시간에는 java 라이브러리인 jjwt를 이용하여 jwt를 생성하고 검증하는 실습 예제를 진행해보겠습니다.

감사합니다.

참조

[1] https://www.rfc-editor.org/rfc/rfc7519

[2] https://github.com/jwtk/jjwt

[3] https://jwt.io/introduction

[4] https://www.rfc-editor.org/rfc/rfc7519#section-4.1

[5] https://www.rfc-editor.org/rfc/rfc7515#section-4.1.1

댓글