분산 웹 서비스와 JWT

들어가며

6월 컬럼 에서, 분산 시스템에서 인증 처리를 위해서 토큰을 사용하는 부분에 대해서 다뤘다.

많은 웹 서비스들이 외부 서비스와 연동할 이유로, 혹은 외부에 API를 제공하기 위해서 OAuth 토큰을 지원한다.

즉, 이렇게 API 공급하는 쪽이,

  • 사용자를 인증하는 일
  • 인증한 사용자가 특정 API를 호출하는 일

두 가지를 쉽게 분리해서 처리할 수 있게 해준다.

인증/접근 토큰의 한계

이전 컬럼에서도 잠시 다뤘듯이, 토큰은 용도가 용도다 보니 다른 용도로 쓸 때는 부적당할 때가 있다.

  • 토큰에 대한 메타 정보가 포함되어 있지 않다.
  • 토큰이 정말로 발급자가 발급한 것인지 바로 확인할 수 없다.

이걸 확인하기 위해서, 발급자에게 토큰에 대한 정보를 확인해, 필요한 메타 정보를 얻고, 정말로 유효한 토큰인지 확인한다. 이 중에서 유효성 확인 부분은 토큰에 대한 전자 서명이 있는 경우에는 별도 통신 없이도 확인할 수 있다. 하지만 표준화된 방법이 있는 것은 아니라서, 널리 쓰기는 쉽지 않다.

지연 시간 문제

토큰을 확인하기 위해서 별도로 통신하는게 어떤 문제인지 생각해보자.
발급자에게 토큰을 확인한다면 다음과 같은 방식으로 통신이 이뤄진다.

Token 기반 메시지 흐름

인증 서버와 API 서버가 토큰을 확인하기 위해 통신하는 4, 5에서 걸리는 지연 시간이 전체 성능에 영향을 준다.
즉, 클라이언트는 순수하게 API호출을 위해 사용하는 시간에 더해서 4, 5 단계에 걸리는 시간 — 두 서버가 같은 네트워크에 있다면 1ms 정도, 서로 다른 네트워크라면 수십-수백 ms 정도 — 의 지연 시간을 겪게 된다.

JWT 를 OAuth 토큰으로 사용하기

JSON Web Token

RFC 7519 JSON Web Token (JWT) 에서는 JWT를 다음과 같이 설명한다.

JSON Web Token (JWT) is a compact, URL-safe means of representing
claims to be transferred between two parties. The claims in a JWT
are encoded as a JSON object that is used as the payload of a JSON
Web Signature (JWS) structure or as the plaintext of a JSON Web
Encryption (JWE) structure, enabling the claims to be digitally
signed or integrity protected with a Message Authentication Code
(MAC) and/or encrypted.

  • 두 주체 사이에 전달할 용도의 가볍고, URL 에 사용하는데 문제 없게 특정 사항을 표현할 수 있는 방법
  • 이 JWT의 내용은 서명 (MAC) 을 포함한 JWS 나 암호화한 JWE 에 담아서 넘긴다.

이 두 가지 부분을 통해서 위에서 말한 두 가지 한계를 극복할 수 있다.

  • 즉 JWT의 내용 (claim) 에 메타 데이터를 넘길 수 있다.
  • JWS 서명 혹은 JWE 암호화를 이용해서 보낸 주체가 보낸 것을 변조하지 않았음을 알 수 있다.

어떻게 이런 용도로 쓰는지에 대해선 뒤에서 다시 설명하도록 하겠다.

JWT barer 토큰을 OAuth 인증 용도로 이용하기

RFC 7523 JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants 에서는 아예 이런 용도로 쓰는 것에 대해서 제안하기도 했다.

예를 들어, POST https://api.example.com/v1/token.oauth2 가 있다면 다음과 같은 요청을 전송한다.

POST /v1/token.oauth2 HTTP/1.1
Host: api.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
client-assertion-type%3Ajwt-bearer&
client_assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjIyIn0.
eyJpc3Mi[...생략...].
cC4hiUPo[...생략...]

JWT가 해결하는 문제들

JWT를 API 호출에 이용하기

우선 간단한 예로, 좀 더 단순하고, JWT 원래 목적에 좀 더 부합하게 bearer token 으로 API에 써보겠다.
예를 들어 GET https://api.example.com/v1/cat/ 이라는 APi가 있다고 치자.

GET /v1/cat/ HTTP/1.1
Host: api.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: bearer eyJhb[...생략...].eyJpc[...생략...].cC4hi[...생략...]

이런 형식으로 호출하면 고양이 목록을 보내준다고 치자.

JWT 구조

Auth0 의 JWT 페이지 를 이용해서 디버깅해보면 쉽게 따라갈 수 있는 내용이다.
우선 JWT는 세 부분으로 이뤄져 있다.
JWT (JWS) 토큰 하나는 eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCJ9.eyJp[...생략...].rnHW[...생략...] 처럼 생겼다.
이 내용은 . 을 기준으로 세 부분으로 분리해서,

  • JOSE header (JSON object signature and encryption header)
  • Claim: JWT 가 포함하는 내용에 대한 것
  • Signature: JWT 헤더와 claim 부분에 대한 서명 (MAC)

이렇게 구분하며, 각각 base64 (URL safe한 변형) 으로 인코딩되어 있다.
흔히 쓰는 (표준) base64 대신에 URL safe 한 변형 — / 대신 _, + 대신 - 를 사용 — 을 쓰는 것은, JWT 가 URL의 일부 (GET param 이라거나)나 HTTP header 로 넘기려는 목적이라서다.

Header

JOSE 헤더를 보면,

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

대부분 간단한 두 가지 내용만 포함된다. 우선 이게 JSON web token이라는 typ 필드. 그리고 어떤 방식의 서명을 쓰는지에 대한 alg 필드가 있다. 여기서는 RSA + SHA2 (256) 을 사용한다. 가능한 algorithm (=alg) 에 대해서는 서명 부분에서 다시 설명하겠다.

참고로, compact 한 표현형을 쓰는게 표준의 목적이라, 정의하고 있는 필드 명은 대부분 짧다. (3자이하)

Claim

토큰이 어떤 내용 을 가지고 있는지에 대한 부분이다. 꼭 필요한 필드는 따로 없으며, 대부분 포함하는 필드 — 예를 들어 만료 시간인 exp — 가 있기는 하다.. 예를 들면, 아래와 같은게 가능하다.

{
"iss": "john.doe",
"aud": "client-id",
"iat": 1212050400,
"exp": 1512054000,
"permissions": [{"type": "cat", "action": ["read", "create", "updtae"]}]
}

발행 주체 (iss) 가 아무개이고, 만료 시간은 12-01이다. 위에 보이는 permissions 는 따로 표준으로 정하는 주체가 없는 private claim 이라고 부르는 맘대로 정해서 쓴 부분이다. (iat, aud 등도 흔히 쓰이는 공개 필드이지만, 여기서의 설명은 생략한다.)

Signature

서명이라고하기엔 조심스러운게, 여기에 올 수 있는 것은 전자 서명이 아니라 MAC (메시지 인증 코드; message authentication code) 다. 그래서 알고리즘에 따라서는
메시지가 변경되었는지 (tampered) 확인하려면 인증 서버에 다시 물어봐야할 수 있다.

여기서 alg 에 해당하는 알고리즘으로 사용하는 것은

  • HS256: HMAC + SHA256
  • RS256: RSA + SHA256
  • ES256: ECDSA + SHA256

같은 값이다. (MAC 알고리즘과 HASH bit 수에 따라 몇 가지 버전이 더 있다.)
이 중 RS256, ES256 만 전자 서명 알고리즘이고, HS256은 HMAC 알고리즘이다. 만약 HS256 혹은 이와 유사한 알고리즘을 쓰면 발행 주체와, 토큰 처리하는 주체가 같은 비밀 값을 공유해야 서명을 처리할 수 있다. (공개키 서명 방식인 RS256, ES256 같은 류는 공개키만 미리 전달받으면 직접 검증할 수 있다)

JWT + 공개키 서명을 쓴 경우의 흐름

JWT 공개키 서명 처리

이전 다이어그램과 비교해보면 토큰 확인을 위해서 인증 서버와 API서버가 통신하는 부분이 빠졌다.
JWT claim 을 해석하고 서명을 검증하는 작업은 수십-수백 us 정도의 작업이라, 이전과 비교하면 지연 시간에 상당한 이득이 있다.

문제 해결

즉, 위 내용의 claim 으로 메타 정보가 없는 문제를 해결한다.
그리고 (특정 서명 알고리즘에 한해서) 원격 서버에 다시 물어보지 않고도 토큰이 올바른지 확인할 수 있다. 다만 exp 부분을 써서 만료 시한을 따로 관리하지 않는다면 이 부분은 문제가 될 수 있으니 구현할 때 확인해보길 권장한다.

맺으며

일정 규모 이상의 게임 서비스든 웹 서비스든, 우리가 만드는 시스템은 더 많은 요청을 처리하기 위해서 결국 분산 시스템의 형태를 갖게 된다.
컴퓨터 공학의 많은 문제가 “추상화 계층”을 추가하는 방식으로 문제를 해결하는데 (혹은 숨기는데), 여기서도 인증 부분을 별도 서비스로 분리시키는 방법을 쓰는 경우가 흔하다.
이런 서비스 간 분리가 있으면 필연적으로 통신에 따른 지연 시간이 문제가 되는데, 표준화된 토큰인 JWT와 공개키 서명 방법을 이용해서 어느 정도 이 문제를 줄일 수 있다.

 

아이펀팩토리 김진욱 CTO

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중