- Published on
JWT의 단점과 주의사항
- Author
- Name
- yceffort
Cookies
웹 애플리케이션을 설계 할 때, 어떻게 사용자를 로그인시키고, 해당 요청 사이에 사용자 정보를 유지시킬 지에 대한 고민을 해본 경험이 있을 것이다. 이를 위해 사용하는 핵심 메커니즘은 쿠키다. 쿠키는 서버가 클라이언트에 보내는 작은 문자열이다. 클라이언트가 이 문자열을 수신한 뒤에, 이후 요청에서 이 문자열을 반복한다. 예를 들어 쿠키에 user_id
를 저장할 수 있으며, 향후 요청시에 클라이언트의 user_id
를 반복적으로 알아 낼 수 있다.
Cookie: USER_ID=123
하지만 이는 보안에 취약하다. 이 정보는 브라우저에서 볼 수 있으며, 사용자가 user_id
를 변경해서 다른 유저인 척 할 수도 있다.
Sessions
이 문제를 해결하는 방법으로는 session이 있다. 종종 세션과 쿠키는 서로 다른 것으로 묘사되지만, 그렇지 않다. 세션이 작동하기 위해서는 쿠키가 필요하다.
Cookie: MY_SESSION_ID=WW91IGdvdCBtZS4gRE0gbWUgb24gdHdpdHRlciBmb3IgYSBmcmVlIGNvb2tpZQ
user_id
를 예측할 수 있게 하는 대신에, 클라이언트에 완전히 랜덤으로 생성된 session id를 전송하여 이게 무엇인지 알 수 없게 한다. ID는 그 외에 어떠한 의미도 가족 있지 않으며, 무엇으로도 decode 될 수 없다. 이를 opaque token
이라고도 한다.
클라이언트가 서버로 반복해서 이 session id를 보내면, 서버는 이 id를 참고하여 데이터베이스에서 유저를 식별하여 연결 시킨다. 사용자가 로그아웃을 하게 되면, 이 session id는 데이터베이스에서 사라지게 되며, 더 이상 이 쿠키로는 해당 사용자를 식별 할 수 없게 된다.
세션 데이터는 어디에 저장되어야 하는가?
PHP와 같은 경우에는 기본적으로 로컬 파일시스템에 데이터를 저장할 수 있다. node.js에는, 메모리에 데이터를 저장하며 서버가 재시작 되면 해당 데이터는 사라지게 된다.
이러한 접근 방식은 개발자 컴퓨터나, 배포가 거의 없는 호스팅 사이트에서는 정상적으로 작동할 수도 있지만, 요즘 배포는 완전히 새로운 시스템을 구성해서 구축하므로, 이 정보를 서버보다 오래 사는 곳에 저장해야 한다. 가장 쉽게 선택할 수 있는 옵션은 데이터 베이스다. Redis, memcached를 사용하는 것이 일반적이다. 이 시스템은 사이트의 규모에 상관 없이 잘 작동한다.
암호화된 토큰
몇년 전 부터 JWT가 등장했다. JWT
는 JSON객체를 암호화/서명하는 표준이며, 인증을 처리하는데 사용된다. 쿠키에 앞서 설명한 opaque token
을 사용하는 대신에, 실제 user_id
를 심어둘 수 있는데, 여기에 서명이 추가된다. 이 서명은 서버에서만 생성될 수 있으며, 이는 secret
값을 활용하여 계산되고 쿠키에 데이터 형태로 저장된다.
이 말인 즉슨, 데이터가 변형될 경우 (user_id
가 변경될 경우) 더 이상 이 서명은 의미가 없게 된다는 뜻이다.
이 방법의 가장 큰 장점은, 시스템에서 Redis나 다른 DB를 사용할 필요 없이 세션 데이터를 저장할 수 있다는 것이다. 모든 정보는 JWT에 있으며, 인프라가 더욱 가벼워 질 수 있다. 별도로 사용자 정보를 요청할 필요가 없으므로 데이터 요청이 조금더 가벼워 질 수 있다.
단점
그러나 이러한 JWT에는 몇가지 단점이 존재한다.
첫번째로, JWT는 매우 복잡한 표준이어서 사용자가(개발자) 잘못 이해할 가능성이 있다. 설정이 잘못되는 경우 최악에 모든 사용자가 유효한 JWT를 생성해서 다른 사용자인척 할 수 있다. 이는 초보개발자들 사이에서 나오는 실수가 아니고, 실제로 Auth0에서 이러한 버그를 만들어낸 적이 있다. 이는 많은 보안 전문가들이 JWT를 싫어하게 되는 이유 중 하나가 되었다. JWT는 수많은 기능을 제공하고 있기 때문에, 개발자들이 잠재적으로 실수 할 수 있는 범위가 넓다.
두번째로, 로그아웃에 문제가 있다. 전통적인 세션을 활용하는 방법의 경우, 단순히 세션 스토리지에서 해당 세션 값을 날리면 되었고 이것으로 충분했다. 그러나 JWT와 다른 무상태 (stateless) 토큰으로는 이것이 불가능하다. 우리는 이 토큰을 지울 수 없다. 왜냐하면 이는 스스로 정보를 담고 있는 토큰이며, 토큰의 상태를 관리하는 중앙 인증 관리 시스템이 없기 때문이다. 이는 아래 세가지 방법으로 해결할 수 있다.
- 토큰의 생명 주기를 굉장히 짧게 하는 방법 (5분 이내). 해당 시간이 지나면, 새롭게 토큰을 만든다.
- 시스템에서 최근에 만료된 토큰을 저장해 두는 것
- 서버 로그아웃 기능 자체를 없애고, 클라이언트에 토큰 삭제를 맡기는 방법 좋은 시스템의 경우엔 앞에 두가지 방법을 선택한다. 그러나 두 문제의 해결책에서 볼 수 있는 것처럼, 두 방법은 모두 중앙에서 인증을 관리해야 하는 시스템이 필요하며 이는 더 이상 JWT의 장점을 유효하지 않게 만든다.
마지막으로, JWT의 크키는 상대적으로 크기 때문에, 쿠키에 JWT를 담을 경우 오버헤드가 발생한다는 것이다.
그런데 왜 유명해졌을까?
기술 블로그를 읽을 때 놀라운 것 중 하나는, JWT에 대해 논하는 사람이 많다는 것이다. 그렇다고 JWT가 세션 토큰 보다 인기 있다는 것은 아니다. 마찬가지로 GraphQL이 REST 보다, No SQL이 관례형 데이터베이스보다 인기 있다는 것은 아니다. 10년 이상 시도되고 테스트된 기술에 대해 이야기하는 것은, 그다지 흥미롭지 않기 때문이다. 신기술은 이전 기술보다 더 많은 화제를 불러일으키고, 사람들이 이 신기술에 대해 계속 이야기 한다면, 단순히 차선책임에도 불구하고 실제 채택으로 이루어질 수도 있다.
이것은 마치 요즘 새로운 개발자들이 서버에서 렌더링되는 HTML을 배우기전에, 리액트로 SPA를 배우는 것과 비슷하다. 경험 많은 개발자들은 서버 사이들 렌더링 HTML이 기본이 되어야 한다고 느낄 것이다. 그리고 필요할 때 SPA를 사용해야 하지만, 새로운 개발자들은 일반적으로 그렇게 배우고 있지 않다.
JWT를 선호하는 이유 중 가장 큰 것은 '확장성' 이다. 그러나 사람들은 '어떤 규모의 확장성' 에서 문제를 겪게 될지를 인지하고 있지는 못하는 것 같다. 대부분의 사람들이 생각하는 문제가 발생할 수 있는지점은, 일반적으로 가정하고 있는 지점 보다 훨씬 더 높은 곳에 있을 것이다.
예를 들어, 우리가 운영하는 사이트는 페이스북 규모가 아니다. 페이스북 규모 정도, 몇백만개의 유효한 세션이 존재할 수 있는 상황에서야 비로소 key-value 세션의 문제가 나타날 수 있다.
그러나 통계적으로, 우리가 만드는 라즈베리파이에서도 쉽게 돌아갈 수 있는 애플리케이션이다.
결론
JWT를 사용하면, 속성을 추가할 수 있고 경우에 따라 서비스가 stateless 해질 수도 있으며, 이는 일부 아키텍쳐에서 바람직한 모습이 될 수도 있다. 그러나 여기에는 단점이 존재한다. 단순히 세션 저장소와 opaque token
을 추가하는 것보다 훨씬 더 복잡한 인프라를 구축해야 한다. 단순히 JWT를 쓰지말라는 것은 아니라, 선택을 함에 있어서 매우 신중해야 한다는 것이다. 보안 및 기능의 트레이드 오프에 유의해야 한다. bolierplate의 템플릿에 넣지말고, 기본 값으로 JWT를 채택해서는 안된다.