[JWT] JSON Web Token 소개 및 구조


지난 포스트에서는 토큰 기반 인증 시스템의 기본적인 개념에 대하여 알아보았습니다. 이 포스트를 읽기 전에, 토큰 기반 인증 시스템에 대해서 잘 모르시는 분들은 지난 포스트를 꼭 읽어주세요.

이번 포스트에서는, 토큰 기반 인증 시스템의 구현체인 JWT (JSON Web Token) 에 대한 소개를 하고, JWT 토큰의 기본 구조를 알아보도록 하겠습니다.

JSON Web Token 이 뭘까?

기본 정보

JSON Web Token (JWT) 은 웹표준 (RFC 7519) 으로서 두 개체에서 JSON 객체를 사용하여 가볍고 자가수용적인 (self-contained) 방식으로 정보를 안전성 있게 전달해줍니다.

수많은 프로그래밍 언어에서 지원됩니다

JWT 는 C, Java, Python, C++, R, C#, PHP, JavaScript, Ruby, Go, Swift 등 대부분의 주류 프로그래밍 언어에서 지원됩니다.

자가 수용적 (self-contained) 입니다

JWT 는 필요한 모든 정보를 자체적으로 지니고 있습니다. JWT 시스템에서 발급된 토큰은, 토큰에 대한 기본정보, 전달 할 정보 (로그인시스템에서는 유저 정보를 나타내겠죠?) 그리고 토큰이 검증됐다는것을 증명해주는 signature 를 포함하고있습니다.

쉽게 전달 될 수 있습니다

JWT 는 자가수용적이므로, 두 개체 사이에서 손쉽게 전달 될 수 있습니다. 웹서버의 경우 HTTP의 헤더에 넣어서 전달 할 수도 있고, URL 의 파라미터로 전달 할 수도 있습니다.

 

JWT 는 어떤 상황에서 사용될까?

다음과 같은 상황에서 JWT 가 유용하게 사용 될 수 있습니다:

  • 회원 인증: JWT 를 사용하는 가장 흔한 시나리오 입니다. 유저가 로그인을 하면, 서버는 유저의 정보에 기반한 토큰을 발급하여 유저에게 전달해줍니다. 그 후, 유저가 서버에 요청을 할 때 마다 JWT를 포함하여 전달합니다. 서버가 클라이언트에게서 요청을 받을때 마다, 해당 토큰이 유효하고 인증됐는지 검증을 하고, 유저가 요청한 작업에 권한이 있는지 확인하여 작업을 처리합니다.
    서버측에서는 유저의 세션을 유지 할 필요가 없습니다. 즉 유저가 로그인되어있는지 안되어있는지 신경 쓸 필요가 없고, 유저가 요청을 했을때 토큰만 확인하면 되니, 세션 관리가 필요 없어서 서버 자원을 많이 아낄 수 있죠.
  • 정보 교류: JWT는 두 개체 사이에서 안정성있게 정보를 교환하기에 좋은 방법입니다. 그 이유는, 정보가 sign 이 되어있기 때문에 정보를 보낸이가 바뀌진 않았는지, 또 정보가 도중에 조작되지는 않았는지 검증할 수 있습니다.

 

JWT 의 생김새

JWT 는 . 을 구분자로 3가지의 문자열로 되어있습니다. 구조는 다음과 같이 이루어져있습니다:

jwt자, 그럼 이렇게 3가지 부분으로 나뉘어져 있는 토큰을 하나하나 파헤쳐봅시다.

JWT 토큰을 만들때는 JWT 를 담당하는 라이브러리가 자동으로 인코딩 및 해싱 작업을 해줍니다. 하지만 이 포스트에서는 JWT 토큰이 만들어지는 과정을 더 잘 파악하기 위해 하나하나 Node.js 환경에서 인코딩 및 해싱을 하도록 하겠습니다.

헤더 (Header)

Header 는 두가지의 정보를 지니고 있습니다.

typ: 토큰의 타입을 지정합니다. 바로 JWT 이죠.

alg: 해싱 알고리즘을 지정합니다.  해싱 알고리즘으로는 보통 HMAC SHA256 혹은 RSA 가 사용되며, 이 알고리즘은, 토큰을 검증 할 때 사용되는 signature 부분에서 사용됩니다.

한번, 이 예제를 살펴보세요 :

(위 예제에서는 HMAC SHA256 이 해싱 알고리즘으로 사용됩니다)

이 정보를 base64 로 인코딩을 하면 다음과 같습니다.

Node.js 환경에서 인코딩하기

자, 이제 JWT 의 첫번째 파트가 완성되었습니다!

참고: JSON 형태의 객체가 base64 로 인코딩 되는 과정에서 공백 / 엔터들이 사라집니다. 따라서, 다음과 같은 문자열을 인코딩을 하게 되죠:
{"alg":"HS256","typ":"JWT"}

 

정보 (payload)

Payload 부분에는 토큰에 담을 정보가 들어있습니다. 여기에 담는 정보의 한 ‘조각’ 을 클레임(claim) 이라고 부르고, 이는 name / value 의 한 쌍으로 이뤄져있습니다. 토큰에는 여러개의 클레임 들을 넣을 수 있습니다.

클레임 의 종류는 다음과 같이 크게 세 분류로 나뉘어져있습니다:

등록된 (registered) 클레임,
공개 (public) 클레임,
비공개 (private) 클레임

그럼, 하나 하나 알아볼까요?

#1 등록된 (registered) 클레임

등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다. 등록된 클레임의 사용은 모두 선택적 (optional)이며, 이에 포함된 클레임 이름들은 다음과 같습니다:

  • iss: 토큰 발급자 (issuer)
  • sub: 토큰 제목 (subject)
  • aud: 토큰 대상자 (audience)
  • exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야합니다.
  • nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념입니다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다.
  • iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있습니다.
  • jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용합니다.

#2 공개 (public) 클레임

공개 클레임들은 충돌이 방지된 (collision-resistant) 이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는, 클레임 이름을 URI 형식으로 짓습니다.

#3 비공개 (private) 클레임

등록된 클레임도아니고, 공개된 클레임들도 아닙니다. 양 측간에 (보통 클라이언트 <->서버) 협의하에 사용되는 클레임 이름들입니다. 공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있으니 사용할때에 유의해야합니다.

예제 Payload

위 예제 payload 는 2개의 등록된 클레임, 1개의 공개 클레임2개의 비공개 클레임으로 이뤄져있습니다.

위 데이터를 base64 로 인코딩을 해볼까요?

Node.js 환경에서 인코딩하기

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

JWT 토큰의 두번째 파트가 완성되었습니다!

 

서명 (signature)

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

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

이렇게 만든 해쉬를, base64 형태로 나타내면 됩니다 (문자열을 인코딩 하는게 아닌 hexbase64 인코딩을 해야합니다)

 

한번, 이 포스트에서 사용된 예제 헤더와 정보를 해싱 해볼까요?

먼저, 헤더와 정보의 인코딩 값 사이에 . 을 넣어주고, 합칩니다

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0

이 값을 비밀키의 값을 secret 으로 해싱을 하고 base64로 인코딩하면 다음과 같은 값이 나옵니다.

Node.js 환경에서 해싱 및 인코딩하기

 

이 부분 또한 padding 이 생기면 지워줍니다.

오우! 3번째 부분까지 끝났군요. 그러면 지금까지 구한 값들을 . 을 중간자로 다 합쳐주면, 하나의 토큰이 완성됩니다.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0.WE5fMufM0NDSVGJ8cAolXGkyB5RmYwCto1pQwDIqo2w

이 값을 https://jwt.io/ 의 디버거에 붙여넣어보세요 (JWT.IO 는 브라우저 상에서 JWT 토큰을 검증하고 생성 할 수 있게 해주는 디버거 서비스입니다)

untitled-4

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

마치면서..

이번 포스트에서는 JWT 의 구조가 어떻게 되어있는지, 그리고 어떤 과정을 거쳐서 만들어지는지 배웠습니다. 토큰에서 필요한 정보를 하나하나 인코딩하고 해싱하는것은 그렇게 대단한 작업은 아니지만 조금은 귀찮기도 합니다. 실제로 JWT 를 서비스에서 사용할때는 우리가 직접 base64 인코딩을하거나 SHA256 해싱을 할 일은 없습니다. JWT 담당 라이브러리에 설정만 해주면 자동으로 손쉽게 만들고, 또 검증도 쉽게해주기 때문이지요.

다음 포스트에서는, Node.js 환경에서 jsonwebtoken 이라는 패키지를 사용하여 MongoDB를 연동한 Express.js 웹 서버 프레임워크에서 JWT 기반 인증 시스템을 구현해보겠습니다.

References

JSON Web Token (JWT)

http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html

JWT – Claims and Signing

http://self-issued.info/docs/draft-jones-json-web-token-01.html

JSON Web Token Tutorial: An Example in Laravel and AngularJS

https://www.toptal.com/web/cookie-free-authentication-with-json-web-tokens-an-example-in-laravel-and-angularjs

 

  • Park Eden

    자세히 들여다보니 재미있네요 ㅎㅎ 잘보고갑니다

    • 어플리케이션 세션관리를 하는데에서 좀 불만을 느끼고 있었는데 JWT를 최근들어 사용해보니 참 괜찮은것같습니다 ㅎㅎ

      • Park Eden

        나중에 리액트 메모 튜토리얼 번외편 한번 가셔도 되겠는데요? ㅎㅎ

        • 나중에 리액트에서 예제로 한번 다뤄볼것같습니다 ㅋㅋ

  • Jeong Hwan Kim

    오랜만에 포스팅이 시네요? ㅎㅎ 잘봤습니다

  • Sukyong Hong

    React 관련 좋은글 항상 잘 읽고 있습니다!! ^^
    저도 마침 JWT 적용하려고 고민중이었는데 많은 도움 얻고 갑니다.

  • Hyunseo Kang

    현재 업무에 딱 필요한 정보. 정말 깔끔하고 상세하게 잘 정리하셨습니다. 항상 많은 도움 됩니다.