IT_Programming/Network Programming

[펌][JWT/JSON Web Token] 토큰(Token) 기반 인증에 대한 소개 | JSON Web Token 소개 및 구조 | 로그인 / 인증에서 Token 사용하기

JJun ™ 2016. 12. 29. 11:29



 * 출처

 : https://velopert.com/2350

 : https://velopert.com/2389

 : https://sanghaklee.tistory.com/47




[JWT] 토큰(Token) 기반 인증에 대한 소개


소개

토큰(Token) 기반 인증은 모던 웹서비스에서 정말 많이 사용되고 있습니다. 여러분이 API 를 사용하는 웹서비스를 개발한다면, 토큰을 사용하여 유저들의 인증작업을 처리하는것이 가장 좋은 방법입니다.

토큰 기반 인증 시스템을 선택하는데에는 여러가지 이유가 있는데요, 그 중 주요 이유들은 다음과 같습니다

Stateless 서버

Stateless 서버를 이해하려면 먼저 Stateful 서버가 무엇인지 알아야합니다. Stateful 서버는 클라이언트에게서 요청을 받을 때 마다, 클라이언트의 상태를 계속해서 유지하고, 이 정보를 서비스 제공에 이용합니다. stateful 서버의 예제로는 세션을 유지하는 웹서버가 있습니다. 예를들어 유저가 로그인을 하면, 세션에 로그인이 되었다고 저장을 해 두고, 서비스를 제공 할 때에 그 데이터를 사용하지요. 여기서 이 세션은, 서버컴퓨터의 메모리에 담을 때도 있고, 데이터베이스 시스템에 담을 때도 있습니다. Stateless 서버는 반대로, 상태를 유지 하지 않습니다. 상태정보를 저장하지 않으면, 서버는 클라이언트측에서 들어오는 요청만으로만 작업을 처리합니다. 이렇게 상태가 없는 경우 클라이언트와 서버의 연결고리가 없기 때문에 서버의 확장성 (Scalability) 이 높아집니다.

모바일 어플리케이션에 적합하다

만약에 Android / iOS 모바일 어플리케이션을 개발 한다면, 안전한 API 를 만들기 위해선 쿠키같은 인증시스템은 이상적이지 않습니다. (쿠키 컨테이너를 사용해야하죠). 토큰 기반 인증을 도입한다면, 더욱 간단하게 이 번거로움을 해결 할 수 있습니다.

인증정보를 다른 어플리케이션으로 전달

대표적인 예제로는, OAuth 가 있습니다. 페이스북/구글 같은 소셜 계정들을 이용하여 다른 웹서비스에서도 로그인 할 수 있게 할 수 있습니다.

보안

토큰 기반 인증 시스템을 사용하여 어플리케이션의 보안을 높일 수 있습니다. 단, 이 토큰 기반 인증을 사용한다고 해서 무조건 해킹의 위험에서 벗어나는건 아닙니다.

 

토큰 기반 인증 시스템을 사용하는 서비스들

토큰 기반 인증 시스템은 여러분이 알고있는 많은 서비스들에서 사용되고있습니다.

  

 

왜 토큰을 사용하게 돼었을까?

토큰 기반 인증 시스템이 어떻게 작동하고, 또 이로 인하여 얻을 수 있는 이득에 대하여 알아보기전에, 이 토큰 기반 인증 시스템이 어쩌다가 나타났는지 알아봅시다. 이를 위해선, 과거의 인증시스템이 어떤 방식으로 작동했는지 살펴볼 필요가 있습니다.

서버 기반 인증

기존의 인증 시스템에서는 서버측에서 유저들의 정보를 기억하고 있어야합니다. 이 세션을 유지하기 위해서는 여러가지 방법이 사용됩니다. 메모리 / 디스크 / 데이터베이스 시스템에 이를 담곤 하죠.

서버 기반 인증 시스템의 흐름을 보자면 다음과 같습니다.

 

이런 방식의 인증 시스템은 아직도 많이 사용 되고 있습니다. 하지만, 요즘 웹 / 모바일 웹 어플리케이션들이 부흥하게 되면서, 이런 방식의 인증 시스템은 문제를 보이기 시작했습니다. 예를 들자면, 서버를 확장하기가 어려웠죠.

서버 기반 인증의 문제점

세션

유저가 인증을 할 때, 서버는 이 기록을 서버에 저장을 해야합니다. 이를 세션 이라고 부릅니다. 대부분의 경우엔 메모리에 이를 저장하는데, 로그인 중인 유저의 수가 늘어난다면 어떻게될까요? 서버의 램이 과부화가 되겠지요? 이를 피하기 위해서, 세션을 데이터베이스에 시스템에 저장하는 방식도 있지만, 이 또한 유저의 수가 많으면 데이터베이스의 성능에 무리를 줄 수 있습니다.

확장성

세션을 사용하면 서버를 확장하는것이 어려워집니다. 여기서 서버의 확장이란, 단순히 서버의 사양을 업그레이드 하는것이 아니라, 더 많은 트래픽을 감당하기 위하여 여러개의 프로세스를 돌리거나, 여러대의 서버 컴퓨터를 추가 하는것을 의미합니다. 세션을 사용하면서 분산된 시스템을 설계하는건 불가능한것은 아니지만 과정이 매우 복잡해집니다.

CORS (Cross-Origin Resource Sharing)

웹 어플리케이션에서 세션을 관리 할 때 자주 사용되는 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어있습니다. 따라서 쿠키를 여러 도메인에서 관리하는것은 좀 번거롭습니다.

토큰 기반 시스템의 작동 원리

토큰 기반 시스템은 stateless 합니다. 무상태. 즉 상태유지를 하지 않는다는 것이죠. 이 시스템에서는 더 이상 유저의 인증 정보를 서버나 세션에 담아두지 않습니다. 이 개념 하나만으로도 위에서 서술한 서버에서 유저의 인증 정보를 서버측에 담아둠으로서 발생하는 많은 문제점들이 해소됩니다.

세션이 존재하지 않으니, 유저들이 로그인 되어있는지 안되어있는지 신경도 쓰지 않으면서 서버를 손쉽게 확장 할 수 있겠죠?

토큰 기반 시스템의 구현 방식은 시스템마다 크고작은 차이가 있겠지만, 대략적으로 보면 다음과 같습니다:

  1. 유저가 아이디와 비밀번호로 로그인을 합니다
  2. 서버측에서 해당 계정정보를 검증합니다.
  3. 계정정보가 정확하다면, 서버측에서 유저에게 signed 토큰을 발급해줍니다.
    여기서 signed 의 의미는 해당 토큰이 서버에서 정상적으로 발급된 토큰임을 증명하는 signature 를 지니고 있다는 것입니다
  4. 클라이언트 측에서 전달받은 토큰을 저장해두고, 서버에 요청을 할 때 마다, 해당 토큰을 함께 서버에 전달합니다.
  5. 서버는 토큰을 검증하고, 요청에 응답합니다.



웹서버에서 토큰을 서버에 전달 할 때에는, HTTP 요청의 헤더에 토큰값을 포함시켜서 전달합니다.

 

토큰의 장점

무상태(stateless) 이며 확장성(scalability)이 있다

이 개념에 대해선 지금 반복적으로 이야기하고 있죠? 이는 그만큼 토큰 기반 인증 시스템의 중요한 속성입니다. 토큰은 클라이언트사이드에 저장하기때문에 완전히 stateless 하며, 서버를 확장하기에 매우 적합한 환경을 제공합니다. 만약에 세션을 서버측에 저장하고 있고, 서버를 여러대를 사용하여 요청을 분산하였다면, 어떤 유저가 로그인 했을땐, 그 유저는 처음 로그인했었던 그 서버에만 요청을 보내도록 설정을 해야합니다. 하지만, 토큰을 사용한다면, 어떤 서버로 요청이 들어가던, 이제 상관이 없죠.

보안성

클라이언트가 서버에 요청을 보낼 때, 더 이상 쿠키를 전달하지 않음으로 쿠키를 사용함으로 인해 발생하는 취약점이 사라집니다. 하지만, 토큰을 사용하는 환경에서도 취약점이 존재 할 수 있으니 언제나 취약점에 대비해야 합니다(참조).

Extensibility (확장성)

여기서의 확장성은, Scalability 와는 또 다른 개념입니다. Scalability 는 서버를 확장하는걸 의미하는 반면, Extensibility 는 로그인 정보가 사용되는 분야를 확장하는것을 의미합니다. 토큰을 사용하여 다른 서비스에서도 권한을 공유 할 수 있습니다. 예를 들어서, 스타트업 구인구직 웹서비스인 로켓펀치에서는 Facebook, LinkedIn, GitHub, Google 계정으로 로그인을 할 수 있습니다. 토큰 기반 시스템에서는, 토큰에 선택적인 권한만 부여하여 발급을 할 수 있습니다 (예를들어서 로켓펀치에서 페이스북 계정으로 로그인을 했다면, 프로필 정보를 가져오는 권한은 있어도, 포스트를 작성 할 수 있는 권한은 없죠)

여러 플랫폼 및 도메인

서버 기반 인증 시스템의 문제점을 다룰 때 CORS 에 대하여 언급 했었죠? 어플리케이션과 서비스의 규모가 커지면, 우리는 여러 디바이스를 호환 시키고, 더 많은 종류의 서비스를 제공하게 됩니다. 토큰을 사용한다면, 그 어떤 디바이스에서도, 그 어떤 도메인에서도, 토큰만 유효하다면 요청이 정상적으로 처리 됩니다. 서버측에서 어플리케이션의 응답부분에 다음 헤더만 포함시켜주면 되지요.

Access-Control-Allow-Origin: *

이런 구조라면, assets 파일들(이미지, css, js, html 파일 등)은 모두 CDN 에서 제공을 하도록 하고, 서버측에서는 오직 API만 다루도록 하도록 설계 할 수도 있지요.

웹 표준 기반

토큰 기반 인증 시스템의 구현체인 JWT는 웹 표준 RFC 7519 에 등록이 되어있습니다. 따라서 여러 환경에서 지원이 되며 (.NET, Ruby, Java, Node.js, Python, PHP …) 수많은 회사의 인프라스트럭쳐에서 사용 되고 있습니다 (구글, 마이크로소프트 …)

 

마치면서..

이번 포스트에서는 토큰 기반 인증 시스템에 대한 소개, 그리고 기존의 시스템과 어떠한 차이가 있는지에 대하여 알아보았습니다. 다음 포스트에서는, 이번에 배운 시스템의 구현체인 JWT (JSON Web Token) 에 대해서 알아보고, 그 시스템에서 사용하는 토큰의 구조를 살펴보도록 하겠습니다.

Reference

Stateless vs Stateful Servers

http://orca.st.usm.edu/~seyfarth/network_pgm/net-6-3-3.html

Cookies vs Tokens. Getting auth right with Angular.JS

https://auth0.com/blog/angularjs-authentication-with-cookies-vs-token/

확장성 있는 웹 아키텍처와 분산 시스템

http://d2.naver.com/helloworld/206816

What is token based authentication?

http://stackoverflow.com/questions/1592534/what-is-token-based-authentication

REST API의 이해와 설계 #1-개념 소개

http://bcho.tistory.com/953

The Ins and Outs of Token Based Authentication

https://scotch.io/tutorials/the-ins-and-outs-of-token-based-authentication#introduction

Token-based authentication: security considerations

http://www.kieranpotts.com/blog/token-authentication-security







[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가지의 문자열로 되어있습니다. 구조는 다음과 같이 이루어져있습니다:


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

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

헤더 (Header)

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

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

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

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

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

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

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

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

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
*/
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

자, 이제 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 형식으로 짓습니다.

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

#3 비공개 (private) 클레임

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

{
    "username": "velopert"
}

예제 Payload

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

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

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

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

const payload = {
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
};
// encode to base64
const encodedPayload = new Buffer(JSON.stringify(payload))
                            .toString('base64')
                            .replace('=', '');
console.log('payload: ',encodedPayload);
/* result
payload:  eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0
*/
eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0

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

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

 

서명 (signature)

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

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

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

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

 

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

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

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0

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

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

const crypto = require('crypto');
const signature = crypto.createHmac('sha256', 'secret')
             .update(encodedHeader + '.' + encodedPayload)
             .digest('base64')
             .replace('=', '');
console.log('signature: ',signature);
WE5fMufM0NDSVGJ8cAolXGkyB5RmYwCto1pQwDIqo2w

 

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

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

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0.WE5fMufM0NDSVGJ8cAolXGkyB5RmYwCto1pQwDIqo2w

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

하단의 텍스트가 파란색으로 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

 







[JWT/JSON Web Token] 로그인 / 인증에서 Token 사용하기

Introduction

웹 / 앱 개발을 하면 로그인 과정에서 반드시 만나게 되는 개념이 쿠키-세션이다.
이미 많은 자료와 경험으로 인해 쿠키는 나쁜 놈 세션은 좋은 놈, 로그인은 일단 세션으로 해야지라는 개념이 개발자들의 머릿속에 자리 잡혀있다.
그러나, 최근 들어 IT 인프라 구성에 많은 변화가 생겼다. 웹 기반의 서비스들은 웹과 앱을 함께 서비스하는 것을 넘어 ‘Mobile First’ 앱이 먼저라는 인식까지 생겨났다.
또한, AWS, Azure 와 같은 IaaS 클라우드 서비스가 대중화 되면서 고사양 단일 서버 아키텍쳐에서 중-저사양 다중 서버 아키텍쳐로 변화하고 있다.
이러한 상황에서 더 이상 쿠키-세션 기반 인증 아키텍쳐는 현재의 요구사항을 만족하지 못하고 있다.

현재의 요구 사항을 그나마 충족시키는 Web Token 기반 JWT에 대해서 알아보고 Web Token이 나타난 배경과 장단점에 대해서 알아보겠다.


Part 1. JWT’s Basic Information

JSON Web Token

  • JSON 포맷을 이용한 Web Token
  • Claim based Token
  • 두 개체에서 JSON 객체를 이용해 Self-contained 방식으로 정보를 안전한게 전달
  • 회원 인증, 정보 전달에 주로 사용
  • RFC 7519

Claim based ?

  • Claim : 사용자에 대한 프로퍼티 / 속성
  • 토큰 자체가 정보
  • Self-contained : 자체 포함, 즉 토큰 자체가 정보
  • key / value


Part 2. Web Token

웹 토큰의 필요성

  • CSRF, 기존 시스템의 보안 문제
  • CORS, 도메인 확장시 api로서의 문제
  • Web, Mobile 등 다양한 클라이언트
  • Session 의 한계
  • Scale out 의 한계
  • REST API : REST API는 Stateless를 지향

현재 우리는?

  • 그런데 아직도 Cookie??
  • 쿠키 기반 로그인의 문제점은 너무 많다
  • Server side Session 기반으로 넘어갈 것인가?

쿠키 기반 인증은 두말 할것 없이 변경해야 하고, 보편적 Session으로 갈것인가?


Part 3. Type of Authorization

Cookie - Client Side Storage

문제점 투성이


Session - Sever Side Authorization

Cookie와 차이점은 Cookie는 정보를 클라이언트에 저장하고 Session은 정보를 서버에 저장한다.


Session Problem 1 - 서버 확장시 세션 정보의 동기화 문제

서버가 스케일아웃 돼서 여러 개가 생기면 각 서버마다 세션 정보가 저장된다.
로그인시(서버1), 새로고침(서버2) 로 접근하면 서버는 인증이 안됐다고 판단한다.


Session Problem 2 - 서버 / 세션 저장소의 부하

세션을 각 서버에 저장하지 않고 세션 전용 서버, DB에 저장해도 문제가 생긴다.
모든 요청 시 해당 서버에 조회해야 한다. DB 부하를 야기할 수 있다.


Session Problem 3 - 웹 / 앱 간의 상이한 쿠키-세션 처리 로직

기존의 Client는 웹 브라우저가 유일했다. 그러나 이제는 모바일로 접근하는 경우도 처리해야 한다.
웹 / 앱 의 쿠키 처리 방법이 다르고 또 다른 Client 가 생겨나면 쿠키-세션에 맞게 처리해야 한다.


Token - Self-contained & Stateless

앞의 문제를 해결하는 최선의 방법은 토큰이다.
토큰은 서버의 상태를 저장하지 않는다. 토큰 자체로 정보를 가지고 있기 때문에 별도의 인증서버가 필요없다. 따라서 요청을 받을 서버 자체에서 인증 프로세스를 수행할 수 있다.
또한, JSON 포맷으로 통신하기 때문에 어떤 Client 에서든 Data 통신에 JSON을 이용하면 토큰을 이용할 수 있다.


Part 4. JWT’s Architecture

JWT의 기본 구조

Header . Payload . Signature


Header

JWT 웹 토큰의 헤더 정보

  • typ : 토큰의 타입, JWT만 존재
  • alg : 해싱 알고리즘. (HMAC SHA256 or RSA). 헤더를 암호화 하는게 아니다. 토큰 검증시 사용.
{
"alg" : "HS256",
"typ" : "JWT"
}

위의 내용을 base64 로 인코딩한다. => eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
base 64 는 암호화된 문자열이 아니다. 같은 문자열에 대해서는 항상 같은 인코딩 문자열을 반환한다.


Payload

실제 토큰으로 사용하려는 데이터가 담기는 부분. 각 데이터를 Claim이라고 하며 다음과 같이 3가지 종류가 있다.

  • Reserved claims : 이미 예약된 Claim. 필수는 아니지만 사용하길 권장. key 는 모두 3자리 String이다.
    • iss (String) : issuer, 토큰 발행자 정보
    • exp (Number) : expiration time, 만료일
    • sub (String) : subject, 제목
    • aud (String) : audience,
    • More
  • Public claims : 사용자 정의 Claim.
    • Public 이라는 이름처럼 공개용 정보
    • 충돌 방지를 위해 URI 포맷을 이용해 저장한다.
  • Private claims : 사용자 정의 Claim

    • Public claims 과 다르게 사용자가 임의로 정한 정보
    • 아래와 같이 일반 정보를 저장한다.
  • {
    "name" : "hak",
    "age" : 26,
    }


Signature

Header와 Payload의 데이터 무결성과 변조 방지를 위한 서명
Header + Payload 를 합친 후, Secret 키와 함께 Header의 해싱 알고리즘으로 인코딩

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


JWT 구조

JWT는 [Header Payload Signature] 각각 JSON 형태의 데이터를 base 64 인코딩 후 합친다.
아래와 같은 순서로 . 을 이용해 합친다.
최종적으로 만들어진 토큰은 HTTP 통신 간 이용되며, Authorization 이라는 key의 value로서 사용된다.



JWT 인증 과정



Part 5. Is JWT a Silver bullet?

그렇다면 모든 서비스들은 기존의 쿠키-세션 기반에서 웹 토큰 기반의 인증으로 변경해야할까?

JWT 의 단점 & 도입시 고려사항

  • Self-contained : 토큰 자체에 정보가 있다는 사실은 양날의 검이 될수 있다.

    • 토큰 길이 : 토큰 자체 payload 에 Claim set을 저장하기 때문에 정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있다.

    • payload 암호화 : payload 자체는 암호화 되지 않고 base64로 인코딩한 데이터다.
      중간에 payload를 탈취하면 디코딩을 통해 테이터를 볼 수 있다.
      JWE 를 통해 암호하하거나, payload에 중요 데이터를 넣지 않아야 한다.

  • Stateless : 무상태성이 때론 불편할 수 있다. 토큰은 한번 만들면 서버에서 제어가 불가능하다.
    토큰을 임의로 삭제할 수 있는 방법이 없기 때문에 토큰 만료시간을 꼭 넣어주는게 좋다.

  • tore Token : 토큰은 클라이언트 side에서 관리해야하기 때문에 토큰을 저장해야한다.


Conclusion

HTTP, REST API 의 공통적인 특징은 Stateless(무상태)를 지향한다는 것이다.
Stateful, 즉 상태를 저장하는 서버는 많은 Side-effect를 발생시킬 수 있다.
또한, 서론에 말했듯이 현재의 IT 인프라 구조는 유연한 확장 가능성이 있어야 하는데 기존의 쿠키-세션 기반의 인증을 사용하면 확장 가능한 인프라를 구성하기 힘들다.
기존의 로그인 / 인증을 모두 Web Token 기반으로 변경할 수는 없지만, 앞으로 만들게 될 서비스 특히 RESTful한 API의 인증에는 JWT를 사용해보는 것이 좋을 것이라 생각한다.
( JWT 인증 예제 - https://github.com/SangHakLee/nodejs-jwt-example-ryan).

nodejs-jwt-example-ryan-master.zip


References



nodejs-jwt-example-ryan-master.zip
0.01MB