June 21st 2019
Medium의 글 5 easy steps to understanding JSON web token 번역해 보았습니다.
이 글을 통해서 JSON Web Token(JWT)의 기본 개념과 왜 사용되는지에 대해서 설명하고자 합니다. JWT는 우리의 애플리케이션의 신뢰와 보안에 있어서 중요한 역할을 합니다. JWT는 유저에 대한 정보를 안전한 방법으로 관리할 수 있도록 합니다.
더 자세한 설명을 하기 전, JWT에 대한 정의에 대해서 살펴보도록 하겠습니다.
JSON Web Token(JWT)은 RFC 7519에 저의된 JSON Object이며, 두 당사자 간 정보를 제시하는 안전한 방식의 하나입니다. Token은 Header, Payload, Signature 세 분류로 나뉩니다.
간단하게 말하면, JWT는 아래의 형식을 가지는 string입니다.
header.payload.signature
JWT가 실제로 왜 그리고 어떻게 사용되는지 알기 위해서, 아래와 같은 세 가지 예를 들어보겠습니다. 이 예제의 단위로는 User, Application server, Authentication server가 있습니다. Authentication server는 JWT Token을 유저에게 공급합니다. 이 JWT Token을 이용해 유저는 안전하게 애플리케이션 상에서 소통을 할 수 있습니다.
먼저, 유저는 인증 서버의 로그인 시스템에 따라 로그인을 진행합니다. Username과 password를 사용할 수도 있고, Facebook이나 Google 로그인 등을 사용할 수도 있습니다. Authentication server는 JWT Token을 생성하고, 이것을 user에게 보냅니다. User는 이후 API 요청을 할 때 JWT Token을 함께 보냅니다. Application server는 이 JWT Token이 Authentication server가 발행한 것이 맞는지 확인하는 작업을 합니다. (이 과정에 대해서는 뒤쪽에서 다시 설명하겠습니다.) User가 JWT Token을 첨부해서 API 요청을 보내면, Application은 API 요청이 인증된 User로부터 전달된 것인지 확인할 수 있습니다.
그럼 JWT는 어떻게 구성되고 승인되는지 좀 더 깊게 살펴보도록 하겠습니다.
JWT의 Header는 JWT signature를 산출하는 방법에 대한 정보가 담겨 있습니다. Header는 다음과 같은 형식의 JSON 객체입니다.
{
"typ": "JWT",
"alg": "HS256"
}
위 JSON의 "typ"는 Object가 JWT임을 명시합니다. "alg" 키의 값은 JWT signature component를 생성하는데 사용한 Hashing algorithm을 나타냅니다. 위의 예제에서는 secret key를 이용하는 hashing algorithm인 HMAC-SHA256을 사용합니다. 이는 signature를 산출하기 위해서 사용되는데 자세한 사항은 step 3에서 다루도록 하겠습니다.
JWT의 Payload component는 JWT 안에 저장된 데이터를 말하며, 보통 "claims"라고도 합니다. 예제에서 authentication server는 유저 정보(ID)가 담긴 JWT를 생성합니다.
{
"userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
}
위의 예에서는 한 가지 claim만 payload에 존재하지만, 우리가 원하는 만큼 claims를 추가할 수 있습니다. JWT paylaod에는 보편화된 claims이 존재하며, issuer를 나타내는 "iss", subject를 나타내는 "sub", 만료시간을 나타내는 "exp"가 있습니다. 이러한 필드는 JWT를 생성할 때 유용하지만 필수적인 것은 아닙니다. JWT standard field에 대해 좀 더 알아보고 싶으시면 Wikipedia를 확인해 주세요.
Data의 사이즈는 JWT의 전체 사이즈에 영향을 줄 수 있습니다. 일반적으로 이것은 문제가 되지 않지만, 과도하게 큰 JWT는 성능이나 latency 문제를 가져올 수 있습니다.
Signature는 다음의 pseudo code와 같은 방식으로 생성됩니다.
// signature algorithm
data = base64urlEncode( header ) + "." + base64urlEncode( payload )
hashedData = hash( data, secret )
signature = base64urlEncode( hashedData )
이 알고리즘이 하는 일은 위의 step 1, 2에서 생성한 header와 payload를 base64url로 인코딩하는 것입니다. 그리고 인코딩된 string을 마침표(.)로 연결하고 data에 할당합니다. data는 JWT header에 명시된 알고리즘과 secret key로 해싱됩니다. 그리고 그 결과는 hashedData에 할당됩니다. 이 해싱된 데이터는 base64url로 인코딩되고 JWT signature를 생성합니다.
예제에서, base64url로 인코딩된 header와 payload는 아래와 같습니다.
// header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
// payload
eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ
그리고 secret key와 명시된 알고리즘을 인코딩 후 합쳐진 header와 payload에 적용하면 signature에 필요한 hashed data를 얻을 수 있습니다. 예제에서는 hashedData string을 얻기 위해 HS256와 secret key를 적용하는 것을 의미합니다. hashedData string을 base64url로 인코딩하면 JWT signature를 얻을 수 있습니다.
// signature
-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
여기까지 세 component를 모두 생성했기 때문에, 이제 우리는 JWT를 만들 수 있습니다. 앞의 과정을 다시 한 번 정리해 보면, JWT에 header, payload, signature component가 있고 이를 마침표(.)로 연결합니다. 그리고 signature 생성을 위해 연결된 header와 payload를 base64url로 인코딩합니다.
// JWT Token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
여러분은 jwt.io에서 자신만의 JWT를 만들 수 있습니다.
다시 예제로 돌아사면, authentication server는 이제 user에게 JWT를 전달할 수 있습니다.
JWT는 데이터를 숨기거나 가리기 위해 사용하는 것이 아님을 인지해야 합니다. JWT를 사용하는 이유는 전달된 데이터가 신뢰하는 곳으로부터 생성된 것인지 확인하기 위해서입니다.
앞서 설명했듯이 JWT 안의 데이터는 인코딩 및 인증된 것이며, 암호화 된 것은 아닙니다. 데이터를 인코딩하는 목적은 data의 구조를 변경하기 위해서입니다. 데이터를 인증한다는 것은, 데이터를 받는 측에서 데이터의 출처에 대한 신뢰를 확인할 수 있는것을 의미합니다. 따라서, 인코딩과 인증은 데이터의 보안을 책임지는 것이 아닙니다. 반면, 암호화는 데이터의 보안을 강화하고 인증없이는 접근할 수 없게합니다. 인코딩과 암호화의 차이 및 해싱에 대해 좀 더 자세히 알고 싶으시면 이 글을 참고하세요.
JWT는 인증 및 인코딩과 관련된 작업이기 때문에, 어떠한 민감한 데이터에 대한 보안에 대해서는 책임지지 않습니다.
위의 3가지 구성 단위에서 살펴보았듯이, HS256 알고리즘과 authentication server와 application server만이 알고 있는 secret key에 의해 인증된 JWT를 이용합니다. Application server는 애플리케이션이 인증 절차에 들어가면, authentication server로부터 secret key를 전달 받습니다. 애플리케이션이 secret key를 알기 때문에, 유저가 JWT가 포함되 API 요청을 보내면 애플리케이션은 Step 3에서 실행한 동일한 signature algorithm을 실행합니다. 그리고 이로 생성된 signature와 애플리케이션이 authentication server로부터 받은 signature가 같은지 확인하는 작업으로 유효한 token인지 확인합니다. 만약, signature가 일치한다면, 해당 요청은 유요한 출처로부터 받은 유요한 API 요청임을 의미합니다. 그렇지 않다면, JWT가 유효하지 않음을 의미하고 이것은 애플리케이션에 어떠한 공격이 가해진 것일 수 있습니다. 따라서, JWT 인증은, 애플리케이션과 유저간의 신뢰의 층을 하나 형성한 것이라고 생각할 수 있습니다.
여기까지 JWT가 어떻게 생성되고 검증되는지, 어떠한 방식으로 애플리케이션과 유저간 신뢰를 형성하는지에 대해 알아보았습니다. 이 글은 JWT의 기본을 이해하고 왜 유용한지에 기초를 다질 수 있도록 하는 시작점입니다. JWT는 우리의 애플리케이션에 신뢰와 보안을 추가하는 한 조각의 퍼즐과 같습니다.
이 글에서 설명한 JWT 인증 방법은 symmectric key algorithm(HS256)을 사용합니다. 여러분은 asymmetric algorithm을 사용하는 경우를 제외하고는 유사한 방법으로 JWT 생성을 위한 설정을 할 수 있습니다, asymmetric algorithm(예, RS256)은 authentication server가 secret key를 가지고 있고, application server는 public key를 가지고 있습니다. stackoverflow에서 두 알고리즘의 차이에 대해서 좀 더 자세히 알 수 있습니다.
JWT는 HTTPS에서도 전달될 수 있습니다. HTTPS를 이용하면 비인증 유저가 JWT를 도중에 훔쳐가는 것을 방지할 수 있습니다. 또한, JWT payload에 유요기한을 설정해, 기한이 지난 JWT는 사용하지 못 하도록 하는 것도 중요합니다. 이 글이 도움이 되고 AWS Lambda와 JWT를 사용하고 있다면, 저희의 프로젝트 Vandium을 확인해 주세요.