Learn business/WEB

JWT란?


JSON Web Token 이란?

JSON Web Token은 아래와 같은 형태로 보여집니다. (가독성을 위해서 개행이 포함됐습니다.)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

위 문자열은 아주 간결하고, claim들을 출력(표현)할 수 있도록 대표하며, 문자열 자체의 진위를 검증하는 서명을 포함하고 있습니다. JWT("jot")는 당사자 간(Server ↔ Client, MSA 등)에 정보(claims)를 JSON 객체로 안전하게 전송하기 위한 간결한 개방형 표준(RFC 7519)입니다.

 

JWT는 정보는 전자 서명되어 있으므로 검증 및 신뢰할 수 있습니다. JWT는 HMAC 알고리즘을 통해 생성된 비밀 키를 사용하여 서명할 수 있으며, 또는 RSA나 ECDSA를 통해 생성된 공개/개인 키를 사용하여 서명할 수 있습니다.

 

JWT를 암호화하여 당사자 간에 비밀을 제공할 수도 있지만 서명된 토큰에 더 많은 중점을 두고 있습니다. 서명된 토큰은 그 안에 포함된 claim의 데이터의 무결성을 확인할 수 있습니다. 반면에 암호화된 토큰은 이러한 claim을 제 3자로부터 숨길 수 있습니다. 공개/개인 키를 사용하여 토큰에 서명할 때 서명은 개인 키를 보유하고 있는 당사자만 서명했음을 증명합니다.

 

언제 JSON Web Token을 사용해야 하는가?

  • Authorization(인증): 인증에서 JWT는 가장 흔하게 사용되고 있습니다. 일단 사용자가 로그인을 하고 이후의 요청들은 JWT를 헤더에 포함해서 요청할 것입니다. SSO(Single Sign On, 혹은 MSA)은 JWT를 범용적으로 사용하도록 만든 특성입니다. 왜냐하면 작은 오버헤드와 다른 도메인들에서 쉽게 사용할 수 있기 때문입니다.
  • Information Exchange(정보 교환): JWT는 당사자 간에 정보를 안전하게 전송하는 좋은 방법입니다. 예를 들어 공개/개인 키를 사용하여 JWT에 서명할 수 있기 때문에 발신자가 누구인지 확인할 수 있습니다. 또한 Header와 Payload를 사용하여 서명을 계산하므로 콘텐츠가 변조되지 않았는지 확인할 수도 있습니다.

 

JWT 구조?

간결한 형태의 JSON Web Token은 ( . )으로 구분된 세 부분으로 구성됩니다.

  • Header
  • Payload
  • Signature

그러므로 JWT는 전형적으로 아래와 같은 모양으로 생겼습니다.

xxxxxx.yyyyyyy.zzzzzzz

 

Header

Header는 전형적으로 JWT의 타입과 HMAC SHA256이나 RSA 같은 것으로 사용된 서명 알고리즘으로 구성됩니다. 

{
  "alg": "HS256",
  "typ": "JWT"
}
const header = {
  "typ": "JWT",
  "alg": "HS256"
};

// encode to base64
const encodedPayload = new Buffer(JSON.stringify(payload))
                            .toString('base64')
                            .replace('=', '');

console.log('payload: ',encodedPayload);

/* Result:
header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
*/

위 JSON은 Base64URL로 인코딩되어 JWT의 첫 부분을 나타냅니다.

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

 

Payload

Payload는 Claims를 포함합니다. Claims는 엔터티(일반적으로 사용자) 및 추가 데이터를 뜻합니다. Claims는 등록된 Registerd Claims,  Public Claims 및 Private Claims 세 가지 유형이 있습니다.

 

Registered Claims : 필수는 아니지만 유용하고 상호 운용 가능한 클레임 집합을 제공하기 위해 권장되는 미리 정의된 클레임 집합입니다. 가장 많이 사용(?)되는 claim는 iss(발급자), exp(만료 시간), sub(제목), aud(청중)입니다. 이외는 링크(RFC-7519)를 대신합니다. 

iss : JWT를 발행한 주체를 나타냅니다.
sub : JWT의 주제인 주체를 나타냅니다.
exp : JWT가 처리를 위해 수락되어서는 안 되는 날짜 또는 그 이후의 만료 시간을 나타냅니다.
aud : JWT가 의도한 수신자를 식별합니다. JWT를 처리하려는 각 주체는 수신자의 claim에 있는 특정 값으로 자신을 나타내야 합니다.

JWT가 간결하단 의미는 위 Claim의 이름이 오직 3글자이기 때문입니다.

 

Public Claims : JWT를 사용하는 사람들이 마음대로 정의할 수 있습니다. 그러나 기존에 이미 등록되어 있는 claims와 충돌을 방지하려면 IANA JSON Web Token 레지스트를 참고하거나 충돌 방지 네임스페이스를 포함하는 URI로 정의해야 합니다.

{
    "https://velopert.com/jwt_claims/is_admin": true
}

Private Claims : Registerd Claims이나 Public Claims이 아닌 당사자 간에 사용에 동의하고 정보를 공유하기 위해 생성된 사용자 지정 claims입니다.

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

아래는 Payload 샘플입니다. 2개의 Registerd Claims, 1개의 Public Claims, 2개의 Private Claims으로 이뤄져있습니다.

{
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
}

그리고 위 JSON은 Base64URL로 인코딩되어 JWT의 두번째 부분을 나타냅니다.

서명된 토큰의 경우 이 정보는 변조로부터 보호되지만 누구나 읽을 수 있습니다. 암호화되지 않은 경우 JWT의 페이로드 또는 헤더 요소에 비밀 정보를 넣으면 안됩니다.

 

Signature

Signature 부분을 생성하려면 인코딩된 Header, 인코딩된 Payload, 비밀키, 헤더에 지정된 알고리즘을 가져와서 서명해야 합니다.

예를 들어 HMAC SHA256 알고리즘을 사용하려는 경우 서명은 다음과 같은 방식으로 생성됩니다.

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

Signature은 메시지가 도중에 변경되지 않았는지 확인하는 데 사용되며 개인 키로 서명된 토큰의 경우 JWT의 보낸 사람이 누구인지 확인할 수도 있습니다.

Putting all together

JWT 결과물은 점( . )으로 구분된 3개의 Base64-URL 문자열이며 HTML 및 HTTP 환경에서 쉽게 전달할 수 있습니다. 그리고 SAML과 같은 XML 기반 표준과 비교할 때 더 간결합니다.

 

JWT는 어떻게 동작하는가?

인증에서 사용자가 성공적으로 로그인하면 JSON Web Token이 반환됩니다. Token은 로그인에 성공했음을 증명하는 문자열이므로 보안 문제를 방지하기 위해 세심한 주의를 기울여야 합니다. 일반적으로 토큰을 필요 이상으로 오래 보관해서는 안 됩니다. 또한 보안이 취약하므로 민감한 세션 데이터를 브라우저 저장소에 저장해서는 안 됩니다.

사용자가 보호된 경로 또는 리소스에 액세스하려고 할 때마다 사용자 Agent는 일반적으로 Bearer 스키마를 사용하여 Authorization 헤더에서 JWT를 보내야 합니다.

Authorization: Bearer <token>

이는 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 별도의 인증 저장소가 필요없다는 것입니다. 서버는 Authorization 헤더에 유효한 JWT가 있는지 확인하고 JWT가 있는 경우 사용자는 리소스에 접근할 수 있도록 하면 됩니다. JWT에 필요한 데이터가 포함되어 있으면 특정 작업에 대해 데이터베이스를 쿼리해야 할 필요성이 줄어들 수 있지만 항상 그런 것은 아닙니다.

Token이 Authorization 헤더로 전송되면 CORS(Cross-Origin Resource Sharing)는 쿠키를 사용하지 않으므로 문제가 되지 않습니다.

  1. Application 또는 Client가 Authorization Server에 권한 부여를 요청합니다.
  2. 권한이 부여되면 Authorization Server는 Applicatio에 Access Token을 반환합니다.
  3. 애플리케이션은 Access Token을 사용하여 보호된 리소스(예: API)에 액세스합니다.

 

왜 JSON Web Tokens를 사용해야하나요?

SWT(Simple Web Tokens) 및 SAML(Security Assertion Markup Language Tokens)과 비교하여 JWT의 이점에 대해 이야기해 보겠습니다.

JSON은 XML보다 덜 장황하기 때문에 인코딩될 때 크기도 작아져 간결해집니다. 따라서 JWT는 HTML 및 HTTP 환경에서 전달하기에 좋은 선택입니다. (아래 예시)

보안적 측면에서 SWT는 오직 HMAC 알고리즘으로 암호화된 secret키에 의해서 대칭적으로 서명될 수 있습니다. 그러나 JWT 및 SAML은 X.509 인증서 형식의 공개/개인 키를 사용하여 서명할 수 있습니다.

 

JSON Parser는 객체에 직접 매핑되기 때문에 대부분의 프로그래밍 언어에서 일반적입니다. 반대로 XML에는 document-to-object 매핑이 없습니다. 이러한 이유로 SAML보다 JWT로 작업하기가 더 수월합니다.

사용적 측면에서 JWT가 인터넷에서 통상적으로 사용됩니다. 이는 여러 플랫폼에서 JSON Web Token의 클라이언트 측 처리 용이성을 강조합니다.