본문 바로가기

서버운영 (TA, ADMIN)/정보보안

[정보보안] REST JWT(JSON Web Token) 이란?

JWT(JSON Web Token)을 이용한 API 인증



개인 학습 목적으로 원본블로그로부터 그대로 옮긴 포스팅입니다.

원본 출처는 조대협의 블로그 : http://bcho.tistory.com/999, http://bcho.tistory.com/1000



REST API에 대한 보안과 인증이 화두가 되면서 많이 언급되는 것이 OAuth인데, 근래에 들어서 화두가 되고 있는 것은 JWT(JSON Web Token)이라는 표준입니다.



Claim 기반 토큰의 개념


OAuth에 의해서 발급되는 access_token은 random string으로 토큰 자체에는 특별한 정보를 가지고 있지 않는 일반적인 스트링 형태입니다. 아래는 페이스북에서 발급된 access_token의 형태로 일반적인 문자열 형태임을 확인할 수 있습니다.


API나 서비스를 제공하는 서버 입장에서 그 access_token을 통해서 사용자에 연관된 권한(예를 들어 scope같은 것)을 식별한 뒤 권한을 허용해주는 구조입니다. 즉 서비스를 제공하는 입장에서는 토큰을 가지고 그 토큰과 연관된 정보를 서버쪽에서 찾아야 합니다. (사용자 ID나 권한 등)


JWT는 Claim 기반이라는 방식을 사용하는데, Claim이라는 사용자에 대한 프로퍼티나 속성을 이야기 합니다. 토큰 자체가 정보를 가지고 있는 방식인데, JWT는 이 Claim을 JSON을 이용해서 정의 합니다.


다음은 Claim을 JSON으로 서술한 예입니다. 이 JSON 자체를 토큰으로 사용하는 것이 Claim 기반의 토큰 방식입니다.


{
    "id":"terry"
    ,"role":["admin","user"]
    ,"company":"pepsi"
}


이러한 Claim 방식의 토큰은 무엇이 좋을까요? 이 토큰을 이요해서 요청을 받는 서버나 서비스 입장에서는 이 서비스를 호출한 사용자에 대한 추가 정보는 이미 토큰 안에 다 들어가 있기 때문에 다른 곳에서 가져올 필요가 없다는 것입니다.


"사용자 관리"라는 API 서비스가 있다고 가정해보면, (이 API는 "관리자(admin)" 권한을 가지고 있는 사용자만이 접근이 가능하며, "관리자" 권한을 가지고 있는 사용자는 그 관리자가 속해 있는 "회사(company)"의 사용자 정보만 관리할 수 있습니다.)라고 정의해보면, 이 시나리오에 대해서 일반적인 스트링 기반의 토큰과 JWT와 같은 Claim 기반의 토큰이 어떤 차이를 가질 수 있는지 알아보면 다음과 같습니다.



OAuth 토큰의 경우



1. API 클라이언트가 Authorization Server(토큰 발급서버)로 토큰을 요청합니다. 이때, 토큰 발급을 요청하는 사용자의 계정과 비밀번호를 넘기고, 이와 함께 토큰의 권한(용도)를 요청합니다. 여기서는 일반 사용자 권한(enduser)과 관리자 권한(admin)을 같이 요청하였습니다.

2. 토큰 생성 요청을 받은 Authorization Server는 사용자 계정을 확인한 후, 이 사용자에게 요청된 권한을 부여해도 되는지 계정 시스템 등에 물어본 후, 사용자에게 해당 토큰을 발급이 가능하면 토큰을 발급하고, 토큰에 대한 정보를 내부(토큰 저장소)에 저장해 둡니다.

3. 이렇게 생성된 토큰은 API 클라이언트로 저장이 됩니다.

4. API 클라이언트는 API를 호출할때 이 토큰을 이용해서 Resource Server(API 서버)에 있는 API를 호출합니다.

5. 이때 호출되는 API는 관리자 권한을 가지고 있어야 사용할 수 있기 때문에, Resource Server가 토큰 저장소에서 토큰에 관련된 사용자 계정, 권한 등의 정보를 가지고 옵니다. 이 토큰에 (관리자)admin 권한이 부여되어 있기 때문에, API 호출을 허용합니다. 위에 정의한 시나리오에서는 그 사용자가 속한 "회사"의 사용자 정보만 조회할 수 있습니다. 라는 전제 조건을 가지고 있기 때문에 API 서버는 추가로 사용자 데이터베이스에서 이 사용자가 속한 "회사" 정보를 찾아와야 합니다.

6. API 서버는 응답을 보냅니다.


JWT와 같은 Claim 기반의 토큰 흐름



1. 토큰을 생성 요청하는 방식은 동일합니다. 마찬가지로 사용자를 인증한 다음에 토큰을 생성합니다.

2. 다른 점은 생성된 토큰에 관련된 정보를 별도로 저장하지 않는다는 것입니다. 토큰에 연관되는 사용자 정보나 권한 등을 토큰 자체에 넣어서 저장합니다.

3. API를 호출하는 방식도 동일합니다.

4. Resource Server (API 서버)는 토큰 내에 들어있는 사용자 정보를 가지고 권한 인가 처리를 하고 결과를 리턴합니다.


결과적으로 차이점은 토큰을 생성하는 단계에서는 생성된 토큰을 별도로 서버에서 유지할 필요가 없으며 토큰을 사용하는 API 서버 입장에서는 API 요청을 검증하기 위해서 토큰을 가지고 사용자 정보를 별도로 계정 시스템 등에서 조회할 필요가 없습니다.


그러면 이러한 Claim 기반의 토큰이 JSON이 처음일까? 이미 이전에, XML 기반의 SAML 2.0이 이와 비슷한 개념을 가지고 있습니다. Assertion이라는 개념으로 XML안에 이러한 Claim 정보를 넣어서 넘길 수 있었으나, 문제점은 전체적인 사이즈가 너무 크고, 구조가 복잡하여 쉽게 접근이 어려웠습니다. 더군다나 크기가 크기 때문에 API와 같이 자주 호출해야 하는 경우에는 HTTP 헤더 등에 실어서 보내기가 어렵고, 파싱에 대한 오버헤드가 크기 때문에 적절하지 않았습니다.(주로 다른 사이트나 시스템간의 SSO에서 상호 사용자 인증등을 위해서 사용됩니다. 무겁기는 하지만 표준화가 잘되어 있기 때문에 사용자 인증 시나리오에서는 현재에도 많이 사용되고 있습니다.)

JWT는 이 JSON Claim을 BASE64로 인코딩하여 HTTP Header에 쉽게 넣을 수 있으며, JSON 기반이기 때문에 파싱과 사용이 쉽습니다. 결과적으로 Claim 기반의 토큰은 토큰 자체가 정보를 담음으로써, 토큰을 가지고 서비스나 API 접근을 제어할 때 별도의 작업이 서버에서 필요하지 않으며, 토큰 자체를 서버에서 관리할 필요가 없기 때문에 구현이 상대적으로 단순해집니다.



JWT에 대한 소개


Claim 기반의 토큰에 대한 개념을 대략적으로 이해했다면, 그러면 실제로 JWT가 어떻게 구성되는지에 대해서 살펴보도록 하면 다음과 같습니다.



Claim (메시지) 정의

JWT는 Claim를 JSON형태로 표현하는 것인데, JSON은 "\n"등 개행문자가 있기 때문에, REST API 호출시 HTTP Header등에 넣기가 매우 불편합니다. 그래서, JWT에서는 이 Claim JSON 문자열을 BASE64 인코딩을 통해서 하나의 문자열로 변환합니다.

{
    "id":"terry",
    "role":["admin", "user"],
    "company":"pepsi"
}


문자열을 BASE64 인코딩 한 결과는 다음과 같습니다.


ew0KICAiaWQiOiJ0ZXJyeSINCiAgLCJyb2xlIjpbImFkbWluIiwidXNlciJdDQogICwiY29tcGFueSI6InBlcHNpIg0KfQ0K



변조 방지

위의 Claim 기반의 토큰을 봤으면, 첫번째 들 수 있는 의문이 토큰을 받은 다음에 누군가 토큰을 변조해서 사용한다면 어떻게 맞느냐? 입니다. 이렇게 메세지가 변조되지 않았음을 증명하는 것을 무결성(Integrity)라고 하는데, 무결성을 보장하는 방법 중 많이 사용되는 방법이 서명(Signature)이나 HMAC을 사용하는 방식입니다. 즉 원본 메시지에서 해쉬값을 추출한 후, 이를 비밀 키를 이용해서 복호화 시켜서 토큰의 뒤에 붙입니다. 이게 HMAC방식인데, 누군가 이 메시지를 변조를 했다면, 변조된 메시지에서 생성한 해쉬값과 토큰 뒤에 붙어있는 HMAC 값이 다르기 때문에 메시지가 변조되었음을 알 수 있습니다. 다른 누군가가 메시지를 변조한 후에, 새롭게 HMAC 값을 만들어내려고 하더라도, HMAC은 앞의 비밀키를 이용해서 복호화 되었기 때문에, 이 비밀키를 알 수 없는 이상 HMAC을 만들어 낼 수 없습니다.



서명 생성 방식

그러면 무결성 보장을 위해서 사용할 수 있는 알고리즘이 SHA1-256 HMAC 뿐일까요?

보안요건에 따라서 SHA1-256,384,512. 그리고 공인 인증서(Certification)을 이요한 RS256 등등 다양한 서명 방식을 지원합니다.

그렇다면 JWT 토큰이 어떤 방식으로 서명되어있는지 어떻게 알 수 있을까요?

그래서 JWT 토큰의 맨 앞부분에는 서명에 어떤 알고리즘을 사용했는지를 JSON형태로 정의한 후, 이 JSON을 다시 BASE64 방식으로 인코딩한 문자열을 붙입니다.



전체 메시지 포맷

위에서 설명한, 서명 방식, JSON 기반의 Claim, 그리고 서명(Signature)까지 포함된 전체적인 JWT 토큰의 구조를 보면 다음과 같습니다.


{서명방식을 정의한 JSON을 BASE64로 인코딩}.{JSON Claim을 BASE64 인코딩}.{JSON Claim에 대한 서명}





JWT의 문제점


사용이 쉽고, 서버의 개발 부담을 덜어줄 수 있다는 여러가지 장점을 가지고 있으나, 그만큼 또 단점도 가지고 있습니다.


1. 길이

Claim에 넣는 데이터가 많아질수록, JWT 토큰의 길이가 길어집니다. API 호출등에 사용할 시에, 매 호출마다 헤더에 붙어서 가야하기 때문에, 길이가 길다는 것은 그만큼 네트워크 대역폭 낭비가 심하다는 의미입니다.


2. 한번 발급된 토큰은 값을 수정하거나 폐기가 불가

JWT는 토큰 내에 모든 정보를 다 가지고 있기 때문에, 한번 발급된 토큰에 대한 변경은 서버에서는 더이상 불가능합니다. 예를 들어 토큰을 잘못 발행해서 삭제하고 싶더라도, Sinagture만 맞으면 맞는 토큰으로 인식을 하기 때문에, 서버에는 한번 발급된 토큰의 정보를 바꾸는 일등이 불가능합니다.

그래서 만약에 JWT를 쓴다면, Expire time을 꼭 명시적으로 두도록하고, refresh token등을 이용해서, 중간중간 토큰을 재발행하도록 해야합니다.


3. 보안

JWT는 기본적으로 Claim에 대한 정보를 암호화하지 않는다. 단순히 BASE64로 인코딩만 하기 때문에, 중간에 패킷을 가로채거나, 기타 방법으로 토큰을 취득했으면 토큰 내부 정보를 통해서 사용자 정보가 누출 될 수 있는 가능성이 있습니다. 특히 자바스크립트 기반의 웹 클라이언트의 경우 브라우저상의 디버거등을 통해서 토큰이 노출될 가능성이 높습니다. 그래서, 이를 보완하는 방법으로는 토큰 자체를 암호화하는 방법이 잇습니다. JSON을 암호화하기 위한 스펙으로는 JWE(JSON Web Encryption)이 있습니다.