토큰(Token) 기반 인증은 모던 웹서비스에서 많이 사용된다. API 를 사용하는 웹서비스를 개발한다면, 토큰을 사용하여 유저들의 인증작업을 처리하는것이 가장 좋은 방법이다.
토큰 기반 인증 시스템을 선택하는데에는 여러가지 이유가 있는데, 그 중 주요 이유들은 다음과 같다
Stateless 서버를 이해하려면 먼저 Stateful 서버가 무엇인지 알아야 한다. Stateful 서버는 클라이언트에게서 요청을 받을 때 마다, 클라이언트의 상태를 계속해서 유지하고, 이 정보를 서비스 제공에 이용한다. stateful 서버의 예제로는 세션을 유지하는 웹서버가 있다. 예를들어 유저가 로그인을 하면, 세션에 로그인이 되었다고 저장을 해 두고, 서비스를 제공 할 때에 그 데이터를 사용한다.
여기서 이 세션은, 서버컴퓨터의 메모리에 담을 때도 있고, 데이터베이스 시스템에 담을 때도 있다. Stateless 서버는 반대로, 상태를 유지 하지 않는다. 상태정보를 저장하지 않으면, 서버는 클라이언트측에서 들어오는 요청만으로만 작업을 처리한다. 이렇게 상태가 없는 경우 클라이언트와 서버의 연결고리가 없기 때문에 서버의 확장성 (Scalability) 이 높아진다.
토큰 기반 인증 시스템이 어떻게 작동하고, 또 이로 인하여 얻을 수 있는 이득에 대하여 알아보기전에, 이 토큰 기반 인증 시스템이 어쩌다가 나타났는지 알아보쟈~
이를 위해선, 과거의 인증시스템이 어떤 방식으로 작동했는지 살펴볼 필요가 있다.
기존의 인증 시스템에서는 서버측에서 유저들의 정보를 기억하고 있어야한다. 이 세션을 유지하기 위해서는 여러가지 방법이 사용된다. 메모리 / 디스크 / 데이터베이스 시스템에 이를 담곤 한다.
서버 기반 인증 시스템의 흐름을 보자면 다음과 같다.
이런 방식의 인증 시스템은 아직도 많이 사용 되고 있다. 하지만, 요즘 웹 / 모바일 웹 어플리케이션들이 부흥하게 되면서, 이런 방식의 인증 시스템은 문제를 보이기 시작했다. 예를 들자면, 서버를 확장하기가 어려워서 이다.
유저가 인증을 할 때, 서버는 이 기록을 서버에 저장을 해야한다. 이를 세션 이라고 부른다. 대부분의 경우엔 메모리에 이를 저장하는데, 로그인 중인 유저의 수가 늘어난다면 어떻게될까? 서버의 램이 과부화가 된다.
이를 피하기 위해서, 세션을 데이터베이스에 시스템에 저장하는 방식도 있지만, 이 또한 유저의 수가 많으면 데이터베이스의 성능에 무리를 줄 수 있다.
세션을 사용하면 서버를 확장하는것이 어려워집니다. 여기서 서버의 확장이란, 단순히 서버의 사양을 업그레이드 하는것이 아니라, 더 많은 트래픽을 감당하기 위하여 여러개의 프로세스를 돌리거나, 여러대의 서버 컴퓨터를 추가 하는것을 의미합니다. 세션을 사용하면서 분산된 시스템을 설계하는건 불가능한것은 아니지만 과정이 매우 복잡해집니다.
웹 어플리케이션에서 세션을 관리 할 때 자주 사용되는 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어있습니다. 따라서 쿠키를 여러 도메인에서 관리하는것은 좀 번거롭습니다.
토큰 기반 시스템은 stateless 하다. 무상태. 즉 상태유지를 하지 않는다는 것이다. 이 시스템에서는 더 이상 유저의 인증 정보를 서버나 세션에 담아두지 않는다. 이 개념 하나만으로도 위에서 서술한 서버에서 유저의 인증 정보를 서버측에 담아둠으로서 발생하는 많은 문제점들이 해소된다.
세션이 존재하지 않으니, 유저들이 로그인 되어있는지 안되어있는지 신경도 쓰지 않으면서 서버를 손쉽게 확장 할 수 있다.!
토큰 기반 시스템의 구현 방식은 시스템마다 크고작은 차이가 있겠지만, 대략적으로 보면 다음과 같다:
이 개념은 토큰 기반 인증 시스템의 중요한 속성이다. 토큰은 클라이언트사이드에 저장하기때문에 완전히 stateless 하며, 서버를 확장하기에 매우 적합한 환경을 제공한다. 만약에 세션을 서버측에 저장하고 있고, 서버를 여러대를 사용하여 요청을 분산하였다면, 어떤 유저가 로그인 했을땐, 그 유저는 처음 로그인했었던 그 서버에만 요청을 보내도록 설정을 해야한다. 하지만, 토큰을 사용한다면, 어떤 서버로 요청이 들어가던, 이제 상관이 없다.
클라이언트가 서버에 요청을 보낼 때, 더 이상 쿠키를 전달하지 않음으로 쿠키를 사용함으로 인해 발생하는 취약점이 사라진다. 하지만, 토큰을 사용하는 환경에서도 취약점이 존재 할 수 있으니 언제나 취약점에 대비해야 한다.
여기서의 확장성은, Scalability 와는 또 다른 개념이다. Scalability 는 서버를 확장하는걸 의미하는 반면, Extensibility 는 로그인 정보가 사용되는 분야를 확장하는것을 의미한다. 토큰을 사용하여 다른 서비스에서도 권한을 공유 할 수 있다. 예를 들어서 로켓펀치에서는 Google 계정으로 로그인을 할 수 있다. 토큰 기반 시스템에서는, 토큰에 선택적인 권한만 부여하여 발급을 할 수 있다 (예를들어서 로켓펀치에서 구글 계정으로 로그인을 했다면, 프로필 정보를 가져오는 권한은 있어도, 포스트를 작성 할 수 있는 권한은 없죠)
JWT는 유저를 인증하고 식별하기 위한 토크(Token)기반 인증이다. 토큰은 세션과는 달리 서버가 아닌 클라이언트에 저장 되기 때문에 메모리나 스토리지 등을 통해 세션을 관리했던 서버의 부담을 덜 수 있다. JWT가 가지는 핵심적인 특징이 있다면, 토큰 자체에 사용자의 권한 정보나 서비스를 사용하기 위한 정보가 포함(Self-contained)된다는 것이다. 데이터가 많아지면 토큰이 커질 수 있으며 토큰이 한 번 발급된 이후 사용자의 정보를 바꾸더라도 토큰을 재발급하지 않는 이상 반영되지 않는다.
JWT를 사용하며 RESTful 과 같은 무상태(Stateless)인 환경에서 사용자 데이터를 주고 받을 수 있게된다. 세션(Session)을 사용하게 될 경우에는 쿠키 등을 통해 식별하고 서버에 세션을 저장했지만 JWT와 같은 토큰을 클라이언트에 저장하고 요청시 단순히 HTTP헤더에 토큰을 첨부하는 것만으로도 단순하게 데이터를 요청하고 응답을 받아올 수 있다.
일반적으로 JWT를 사용하면 아래와 같은 순서로 진행된다.
1. 클라이언트 사용자가 아이디, 패스워드를 통해 웹서비스 인증.
2. 서버에서 서명된(Signed) JWT를 생성하여 클라이언트에 응답으로 돌려주기.
3. 클라이언트가 서버에 데이터를 추가적으로 요구할 때 JWT를 HTTP Header에 첨부.
4. 서버에서 클라이언트로부터 온 JWT를 검증.
JWT는 JSON 데이터를 Base64 URL-safe Encode를 통해 인코딩하여 직렬화한 것이 포함되며 토큰 내부에는 위변조 방지를 위해 개인키를 통한 전자서명도 있다. 따라서 사용자가 JWT를 서버로 전송하면 서버는 서명을 검증하는 과정을 거치게 되며 검증이 완료되면 요청한 응답을 돌려준다.
Base64 URL-safe Encode는 일반적이 Base64 Encode에서 URL에서 오류없이 사용하도록 '+','/'를 각각 '-','_'로 표현한 것이다.
JWT의 구조를 보자. JWT는 Header, Payload, Signature로 구성된다. 또한 각 요소는 . 으로 구분된다. Headerd에는 JWT에서 사용할 타입과 해시 알고리즘의 종류가 담겨있으며 Payload는 서버에서 첨부한 사용자 권한 정보와 데이터가 담겨있다. 마지막으로 Signature에는 Header, Payload를 Base64 URL-safe Encode를 한 이후 Header에 명시된 해시함수를 적용하고, 개인키(Private Key)로 서명한 전자서명이 담겨있다. 전자서명 알고리즘으로 타원 곡선 암호화(ECDSA)를 사용한다고 가정하면,
Sig = ECDSA(SHA256(B64Header).B64(Payload)),PrivateKey)
이를 JWT로 표현하려면, 다음과 같이 되는데, 위에서 만든 전자서명도 Base64 URL-safe Encode로 처리해서 합쳐줄 필요가 있다. 여기서 만든 전자서명은 Header, Payload가 변조되었는지 확인하기 위해 사용되는 중요 정보이며 JWT를 신뢰 할 수 있는 토킁으로 사용할 수 있는 근거가 된다.
JWT = B64(Header).B64(Payload).B64(Sig)
전자서명에는 비대칭 암호화 알고리즘을 사용하므로 암호화를 위한 키와 복호화를 위한 키가 다르다. 암호화(전자서명)에는 개인키를, 복호화(검증)에는 공개키를 사용한다.
서버 기반 인증 시스템의 문제점을 다룰 때 CORS 에 대하여 언급이 되어있다. 어플리케이션과 서비스의 규모가 커지면, 우리는 여러 디바이스를 호환 시키고, 더 많은 종류의 서비스를 제공하게 된다. 토큰을 사용한다면, 그 어떤 디바이스에서도, 그 어떤 도메인에서도, 토큰만 유효하다면 요청이 정상적으로 처리 된다. 서버측에서 어플리케이션의 응답부분에 다음 헤더만 포함시켜주면 된다.
Access-Control-Allow-Origin: *
이런 구조라면, assets 파일들(이미지, css, js, html 파일 등)은 모두 CDN 에서 제공을 하도록 하고, 서버측에서는 오직 API만 다루도록 하도록 설계 할 수도 있다.
참고
위키백과
JWT 토큰부터 개념부터 구현까지 알아보기
[JWT] 토큰(Token) 기반 인증에 대한 소개