Home (JWT) JSON Web Tokens 알아보기
Post
Cancel

(JWT) JSON Web Tokens 알아보기

JWT(JSON Web Tokens)란?

JSON web token(RFC 7519) 이란, 인증과 인가 또는 정보교환을 위해 사용되는 웹 표준 토큰입니다.

JWT의 특징 (Features of JWT)

  • 토큰방식은 세션과 다르게, 인증에 필요한 정보들을 클라이언트에서 관리하기 때문에 메모리나 스토리지 등을 통해 세션을 관리했던 서버의 부담을 덜 수 있습니다.

  • JWT는 토큰자체에 사용자의 정보나 서비스에 대한 접근권한 정보(scope) 등이 포함되어 있습니다.
    따라서 저장할 데이터가 많아지면 오버헤드가 커질 수 있습니다.
  • JWT는 무상태(stateless)입니다. 세션의 경우 서버에서 세션ID를 발행해 클라이언트의 쿠키에 저장하고 상태를 유지하여 HTTP의 무상태 특징을 위배하지만,
    JWT의 경우 토큰을 클라이언트에 저장하고 요청 시 단순히 HTTP 헤더에 토큰을 첨부하거나 쿠키로 전송하여 단순하게 데이터를 요청하고 응답을 받을 수 있습니다.

JWT의 구조 (JSON Web Token Structure)

올바른 형식의 JWT는 Header, Payload, Signature로 구성되며, 각 요소는 .으로 구분됩니다. 또한 각 요소는 Base64url 인코딩이 되어 있습니다.

image-20230417122404383 JWT (JSON web token)

image-20230417122808055 JWT (encoded, decoded)

헤더에는 해당 토큰의 타입과 시그니처 생성에 어떤 방식의 알고리즘이 사용되어 있는지 저장합니다.

Payload

페이로드에는 Claim을 저장합니다. 클레임은 key-value 형태의 property이며, 세 종류로 구분할 수 있습니다.

  • 등록된(registered) 클레임
  • 커스텀(custom) 클레임
    • 공개(public) 클레임
    • 비공개(private) 클레임

등록된(registered) 클레임

등록된 클레임은 Internet Assigned Numbers Authority (IANA)에 등록된 표준 클레임입니다. 또한 JWT specification에 정의되어 있습니다.

등록된 클레임의 사용은 모두 선택적(optional)이며, 사용을 권장하는 7개의 등록된 클레임은 다음과 같습니다.

  • iss: 토큰 발급자 (issuer)
  • sub: 토큰 제목 (subject)
  • aud: 토큰 대상자 (audience)
  • exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 합니다.(ex: 1681700400)
  • nbf: NumericDate 형식. Not Before 를 의미하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다.
  • iat: NumericDate 형식. 토큰이 발급된 시간(issued at)입니다.
  • jti: JWT의 고유 식별자입니다. 일회용 토큰에 사용하면 유용합니다.

이곳에서 모든 등록된 클레임들을 확인할 수 있습니다. 👀

커스텀(custom) 클레임

등록되지 않은 공개 또는 비공개 클레임입니다. 비공개클레임은 공개클레임과는 다르게 충돌가능성이 있으므로 유의해서 생성해야 합니다.

공개(public) 클레임

공개클레임을 생성하는 경우 충돌방지를 위해 Internet Assigned Numbers Authority (IANA)에 정의된대로 사용하거나, URI 형식으로 키를 지정하여 생성해야 합니다.

다음은 OpenID Connect(OIDC)에 의해 등록된 공개 클레임의 예시입니다.

  • auth_time
  • acr
  • nonce
    • id token 재생공격 방지를 위해 사용을 권장합니다.
비공개(private) 클레임

통신을 주고받는 당사자들끼리 합의해서 자유롭게 클레임을 생성할 수 있습니다.

Signature

헤더와 페이로드는 암호화를 한 것이 아니라 단순히 JSON 포맷을 base64로 인코딩한 문자열입니다. 따라서, 누구나 이 값을 다시 디코딩하면 JSON에 어떤 내용이 들어있는지 확인할 수 있습니다.

토큰을 사용하는 경우 이 토큰을 다른 사람이 위변조할 수 없어야 하므로, 헤더와 페이로드의 위변조 여부를 검증하기 위한 부분이 Signature 부분입니다.

헤더와 페이로드를 base64url로 인코딩해서 만든 두 값을 .으로 이어 붙이고 헤더에서 alg로 지정한 알고리즘 HS256 즉, HMAC SHA-256으로 암호화하면 JWT 토큰의 세 번째 부분인 Signature가 완성됩니다.

공격자가 서명을 위변조할 수 없도록 비밀키를 사용해서 서명하는데, 이 비밀키는 외부에 노출되면 안 됩니다.

JWT 생성하기 (Libraries for Token Signing/Verification)

https://jwt.io/libraries에서 JWT에 관한 third-party 라이브러리들을 확인할 수 있습니다.

JWT는 각 Header, Paload, Signature 각 요소가 .으로 구분되며 base64url 인코딩 되어 있습니다.

Header

header는 토큰의 타입과 어떤 암호화 알고리즘을 사용하는지 명시되어 있습니다.

1
2
3
4
{
  "typ": "JWT",
  "alg": "HS256"
}

해당 JSON 문자열을 인코딩하면, eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9의 값을 얻을 수 있습니다.

base64로 인코딩을 할 때 dA== 처럼 뒤에 = 문자가 한두개 붙을 때가 있습니다.
이 문자는 base64 인코딩의 padding 문자라고 부릅니다.

JWT 토큰은 가끔 URL 의 파라미터로 전달 될 때도 있는데요, 이 = 문자는, url-safe 하지 않으므로 제거되어야 합니다.
패딩이 한개 생길 때도 있고, 두개 생길 때도 있는데, 전부 지워줍니다. (제거해줘도 디코딩 할 때 전혀 문제가 되지 않습니다)

Payload

토큰에 담을 정보가 들어있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "name": "John Doe", // 공개 클레임
  "nickname": "john.doe", // 공개 클레임
  "picture": "https://myawesomeavatar.com/avatar.png", // 공개 클레임
  "updated_at": "2017-03-30T15:13:40.474Z", // 비공개 클레임
  "email": "john.doe@test.com", // 공개 클레임
  "email_verified": false, // 공개 클레임
  "iss": "https://{yourDomain}/", // 등록된 클레임
  "sub": "auth0|USER-ID", // 등록된 클레임
  "aud": "{yourClientId}", // 등록된 클레임
  "exp": 1490922820, // 등록된 클레임
  "iat": 1490886820, // 등록된 클레임
  "nonce": "crypto-value", // 공개 클레임
  "at_hash": "IoS3ZGppJKUn3Bta_LgE2A" // 공개 클레임
}

해당 JSON 문자열을 인코딩하면, eyJuYW1lIjoiSm9obiBEb2UiLCJuaWNrbmFtZSI6ImpvaG4uZG9lIiwicGljdHVyZSI6Imh0dHBzOi8vbXlhd2Vzb21lYXZhdGFyLmNvbS9hdmF0YXIucG5nIiwidXBkYXRlZF9hdCI6IjIwMTctMDMtMzBUMTU6MTM6NDAuNDc0WiIsImVtYWlsIjoiam9obi5kb2VAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOi8ve3lvdXJEb21haW59LyIsInN1YiI6ImF1dGgwfFVTRVItSUQiLCJhdWQiOiJ7eW91ckNsaWVudElkfSIsImV4cCI6MTQ5MDkyMjgyMCwiaWF0IjoxNDkwODg2ODIwLCJub25jZSI6ImNyeXB0by12YWx1ZSIsImF0X2hhc2giOiJJb1MzWkdwcEpLVW4zQnRhX0xnRTJBIn0의 값을 얻을 수 있습니다.

토큰에 담는 정보가 많아질수록 오버헤드가 커지게 됩니다.

Signature

JSON Web Token 의 마지막 부분은 서명(signature) 입니다. 이 서명은 헤더의 인코딩값과, 정보의 인코딩값을 합친후 주어진 비밀키로 해쉬를 하여 생성합니다.

서명 부분을 만드는 슈도코드(pseudocode)의 구조는 다음과 같습니다.

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

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuaWNrbmFtZSI6ImpvaG4uZG9lIiwicGljdHVyZSI6Imh0dHBzOi8vbXlhd2Vzb21lYXZhdGFyLmNvbS9hdmF0YXIucG5nIiwidXBkYXRlZF9hdCI6IjIwMTctMDMtMzBUMTU6MTM6NDAuNDc0WiIsImVtYWlsIjoiam9obi5kb2VAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOi8ve3lvdXJEb21haW59LyIsInN1YiI6ImF1dGgwfFVTRVItSUQiLCJhdWQiOiJ7eW91ckNsaWVudElkfSIsImV4cCI6MTQ5MDkyMjgyMCwiaWF0IjoxNDkwODg2ODIwLCJub25jZSI6ImNyeXB0by12YWx1ZSIsImF0X2hhc2giOiJJb1MzWkdwcEpLVW4zQnRhX0xnRTJBIn0

이 값을 secret 값으로 해싱을 하고, base64url 로 인코딩하면 다음과 같은 값을 얻을 수 있습니다.

kwveU6IoEuxvDpNcncBJ-mF6T8DJwgxsbWOwMIMmBP0


지금까지 구한 값들을 .을 구분자로 합쳐주게 되면 하나의 JWT가 완성됩니다.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuaWNrbmFtZSI6ImpvaG4uZG9lIiwicGljdHVyZSI6Imh0dHBzOi8vbXlhd2Vzb21lYXZhdGFyLmNvbS9hdmF0YXIucG5nIiwidXBkYXRlZF9hdCI6IjIwMTctMDMtMzBUMTU6MTM6NDAuNDc0WiIsImVtYWlsIjoiam9obi5kb2VAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOi8ve3lvdXJEb21haW59LyIsInN1YiI6ImF1dGgwfFVTRVItSUQiLCJhdWQiOiJ7eW91ckNsaWVudElkfSIsImV4cCI6MTQ5MDkyMjgyMCwiaWF0IjoxNDkwODg2ODIwLCJub25jZSI6ImNyeXB0by12YWx1ZSIsImF0X2hhc2giOiJJb1MzWkdwcEpLVW4zQnRhX0xnRTJBIn0.kwveU6IoEuxvDpNcncBJ-mF6T8DJwgxsbWOwMIMmBP0

image-20230417143525459 jwt.io

Encoded 하단의 텍스트가 파란색으로 Signature Verified 라고 뜨면 JWT 토큰이 검증되었다는 것 입니다.

마침

궁금하시거나, 부족한 내용이 있다면 코멘트 부탁드립니다. 🙇🏻‍♂️

레퍼런스

JSON docs
https://auth0.com/docs/secure/tokens/json-web-tokens#learn-more

JWT decoder
https://jwt.io/

대칭키와 비대칭키에 대하여
https://universitytomorrow.com/22

[JWT] JSON Web Token 소개 및 구조
https://velopert.com/2389

This post is licensed under CC BY 4.0 by the author.

(OAuth2.0) Authorization Code Grant (권한 부여 인증 방식)

(Docker) 도커 튜토리얼, SpringBoot 애플리케이션을 도커를 통해 실행하기

Comments powered by Disqus.