[Security] JWT에 대한 고찰

[Security] JWT에 대한 고찰

JWT는 서버에서 사용자를 식별 할 수 있는 방법 중 하나이다. JWT를 여러번 사용해보고 난 후 생각을 정리해보았다.

1. 경험담

전통적으로 서버가 사용자를 식별하는 방법으로 세션(Session)을 사용해왔다. 그러나 Session 기반 인증은 서버에서 상태를 관리해야 하므로 분산 환경에서 확장이 어렵다는 단점이 있다.

Session이 가진 단점을 보완하기 위해 JWT(JSON Web Token)라는 인증 방식이 등장했다. JWT는 Session 인증과 반대로 클라이언트가 사용자에 대한 정보를 가지고 있어 서버에서 상태 관리를 하지 않아도 된다.

분산 환경에서 구현하기 간편하며, 확장이 쉽다고 한다. 그래서 사이드 프로젝트를 할 때, JWT로 인증을 구현해봤는데… 정말 말처럼 구현하기 간편했고, 확장이 쉬웠을까? 결론은 아니다 그 이유를 설명해보겠다.

2. JWT가 인증 정보를 클라이언트가 가지고 있도록 만드는 방법

Session 인증의 경우는 사용자가 로그인을 하면 Session을 발급해준다. 랜덤으로 생성된 값이기 때문에 Session을 뜯어보아도 누구의 Session인지 알 수 없다. Session은 일종의 키 값으로, 사용자가 Session과 함께 서버로 요청을 하면 메모리 또는 데이터베이스와 같은 저장소에서 Session과 맵핑된 사용자 정보를 가져와 요청을 처리 할 때 활용한다.

JWT는 다르다. jwt.io 사이트에 접속하면 JWT를 테스트 해볼 수 있는데, JWT로 인코드 된 문자열을 보면 .으로 구분하여 세 부분으로 나뉘어 있는 것을 확인 할 수 있다.

그림 1. jwt.io

위 그림에서 보다시피 jwt는 Header, Payload, Verify Signature로 이루어져 있으며, Header와 Payload는 단순히 Base64로 인코드 되어있어 간단하게 원래 데이터로 복구 할 수 있다.

각 부분에 대해서 대해서 간단히 설명하면 Header는 토큰에 대한 정보, Payload는 인증된 사용자의 정보, Verify Signature는 Header와 Payload를 이용해 해싱한 값이다.

서버는 Verify Signature를 통해 JWT가 유효한지 확인하는데, Verify Signature를 만들 때 사용한 키는 서버만 알고있으므로, 해커가 이 키 값을 모른다면 유효한 토큰을 만들 수 없다.

그래도 “어? 그럼 토큰이 탈취되면 사용자의 정보가 그대로 유출되는거 아냐?”라는 위험성에 대해 생각 할 수 있는데, 맞다. 그래서 절대로 Payload에는 사용자의 중요한 정보를 담아서는 안된다.

3. 토큰이 탈취되면 어떡하지?

Session이나 JWT나 탈취되면 위험한것은 마찬가지이다. 그러나 Session의 경우에는 이상을 감지하면 서버에서 Session을 비활성화 처리하면 된다. 하지만 JWT는 발급해주고나면 서버의 손아귀를 벗어나기 때문에 어떻게 할 수 없다.

이를 해결하기 위한 방안으로 Refresh Token, Black List 두 가지 방법이 제안되었다. JWT의 단점은 여기서 드러난다. 우선 각 방법에 대해 간단히 설명해보겠다.

A. Refresh Token

사용자 식별에 사용되는 토큰을 Access Token이라고 하는데, Access Token이 유효한 시간을 짧게 하여 탈취되더라도 피해를 최소화한다.

유효기간이 짧기 때문에 토큰이 만료되면 다시 로그인을 해야하는 번거로움이 있다.

그래서 로그인을 할 때 Refresh Token을 함께 발급하여, 토큰이 만료되었을 때 Refresh Token을 사용하면 토큰을 재발급 해주도록 구성하는 것이다.

B. Black List

Black List는 말 그대로 토큰의 블랙리스트를 관리하는 방법이다. 블랙리스트에 등록된 토큰은 서버에서 거부하는 방법이다.

이상을 감지하면 해당 토큰을 블랙리스트에 등록하는 것이다. 이 방법은 JWT에서 로그아웃을 구현 할 때에도 사용된다.

4. JWT를 채택하기 전 고려해야 할 점

A. 네트워크 오버헤드

JWT에는 인증을 위한 여러가지 정보를 포함하고 있어 Session보다 사이즈가 크다.

로그인 후에는 요청을 보낼 때 마다 토큰을 Request Header에 추가하여 같이 보내야 하기 때문에 Session보다 부담스러울 수 밖에 없다.

특히 필요에 따라 JWT에 정보를 추가하면 추가할수록 네트워크 오버헤드가 증가한다.

B. 결국 Session처럼

사실 JWT만 사용한다면 Session을 대체 할 수 있었을 것이다. 그러나 로그아웃을 위해서 필수라고 할 수 있는 Black List 기능을 추가하면 확장이 어려워진다.

서버들이 Black List를 서로 공유해야 하기 때문에 Redis와 같은 저장소를 사용해야하기 때문이다.

JWT를 사용하는 목적이 클라이언트가 사용자 정보를 가지고 있게 함으로써, 서버간 사용자 데이터를 공유하지 않도록 하여 확장이 쉽도록 만드는 것인데 Black List의 공유로 인해 이러한 목적이 희석되는 느낌이 없잖아 있다.

물론 Session에 비해 관리해야 할 데이터의 사이즈는 적을 수 있기 때문에 Redis 비용을 줄일 수는 있을 것이다.

C. Spring Security에서 공식 지원하지 않아 직접 구현해야 함

Spring에서 로그인 기능을 구현한다면 가장 쉬운 방법으로 Spring Security를 이용하는 것이다. 이미 필요한 기능들이 잘 만들어져있고, 여러가지 설정을 제공하여 원하는 구성을 할 수 있으며, 확장이 편리하다.

반면에 JWT의 경우에는 JWT를 만드는 공식 라이브리가 존재하고, 이 라이브러리와 Spring Security를 이용해 JWT 인증을 구현하지 못하는 것은 아니지만, Spring Security에서 JWT 자체를 지원하지 않기때문에 토큰을 발급하는 과정은 직접 구현해야 한다.

특히 더 높은 수준의 보안을 제공하기 위해 Refresh Token, Black List 기능을 추가해야 한다면 구현은 더 복잡해질 수 밖에 없다.

Refresh Token, Black List는 별도의 라이브러리를 제공하지 않기 때문에 한땀 한땀 직접 구현해야 한다.

따라서 구현하기 간편하다는 말은 맞지 않다.

결론

“Session이 무조건 최고다!” 이런게 아니다. JWT는 훌륭한 인증 방법이지만 빠르게 사업화를 해야하는 Start-Up이나 토이 프로젝트에서는 적절하지 않다는 생각이 많이 들었다.

물론 토이 프로젝트에서 JWT 인증을 구현해보겠다고 하면 다른 이야기지만, 잘 만들어진 Session 인증을 두고 JWT 인증 구현에 시간을 쏟는 것 보다 비즈니스 로직 개발에 집중 하는 것이 더 낫지 않을까?

출처

타이틀 이미지: UnsplashGeorge Prentzas