API 서버의 인증 수단으로 JWT를 사용하는 것이 옳은가?

하루히즘·2022년 1월 27일
5

서론

SimpleBBS 이후 JWT의 존재를 알게 되면서 API 애플리케이션의 인증 방식으로 Stateless한 토큰 방식 인증을 사용하면 확장성이나 인증 측면에서 더 간편하고 좋다! 라는 미신(?)에 빠져있었다. JWT의 장점이라고 알려진 무상태성으로 인한 확장성, 불필요한 데이터베이스 접근 배제, 간편한 사용 등 다양한 측면에서 좋다고 알려져 있기 때문이다.

그런데 지난 게시글에 달린 댓글에서 토큰 만료 기능을 구현하면서 무상태성을 잃게 되면 세션이랑 어떤 차이가 있는지 물어보는 내용이 있었고 그에 대해 답변을 고민하다가 왜 JWT를 세션으로 사용하는가라는 근본적인 물음에 도달할 수 있었다.

확실히 세션이 기본적으로 웹 서버의 메모리에 저장되기 때문에 확장성 측면에서는 불리하긴 하지만 그렇다고 JWT가 세션을 대체할 수 있는 것일까? 이는 조금 고민해 볼 필요가 있다.

본론

JWT의 만료

JWT의 장점 중 하나는 세션 데이터를 클라이언트 쪽에서 관리하기 때문에 서버에서 이를 기억할 필요가 없고(stateless) 비밀키를 포함한 해시값 서명으로 유효성을 검증 및 별도의 인증과정 없이 서버측에서 클라이언트를 인증할 수 있다는 장점이 있다.

그러나 이는 양날의 검이 될 수 있는데 별도의 인증과정이 없다는 것은 클라이언트에게 발급한 토큰이 제 3자에게 노출되어 악용되더라도 서버측에서 제재할 수단이 없다는 것이다. 한 번 발급된 토큰은 그 상태로 명시된 기간까지 유효한 값이기 때문에 중앙집중식으로 관리되지 않는 무상태(stateless)한 특성에 따라 누구도 폐기할 수 없다.

이외에도 클라이언트 측에서 로그아웃이나 보안 대비 측면에서 토큰을 폐기하고 싶을때 클라이언트 애플리케이션에서 토큰을 삭제하면 비슷하게 구현할 수 있겠지만 해당 토큰이 무효가 되는 것은 아니기 때문에 반쪽짜리 해결책이 된다.

최종적인 해결책은 서버측에서 별도의 데이터베이스를 구축해서 만료된 토큰을 저장하거나 특정 사용자가 사용할 수 있는 JWT를 1대1로 맵핑시키는 방법뿐이다. 즉 stateless한 JWT를 stateful하게 관리해야 이를 만료시킬 수 있는 것인데 이런 경우에도 굳이 JWT를 사용해야 할까?

JWT 클레임의 만료

JWT에 만약 사용자 권한이나 기타 개인정보가 담겨있고 해당 정보가 갱신되었다고 할 때 이 갱신된 정보를 어떻게 현재 JWT에 반영할 수 있을까? 불가능하다. 즉 기존 JWT의 클레임은 만료(stale)되고 갱신된 정보를 담는 새로운 JWT를 발급해서 클라이언트에게 전달해야 하는데 이는 위의 토큰 만료기능과 궤를 같이한다.

게다가 본인이 아닌 관리자 등 제 3자가 이를 갱신했을 때 이 변경 사항을 클라이언트에게 실시간으로 전달할 수 있는 방법은 없다. 갱신될 수 있는 정보를 JWT의 클레임에 담지 않는다거나 클라이언트가 토큰의 만료를 확인할 수 있는 방식으로 디자인하면 영향을 덜 받을 수 있지만 이런 식으로 복잡하게 구성하는 것은 다시 한 번 고려해 볼 부분이다.

JWT의 기밀성

세션 ID와 JWT의 가장 큰 차이점 중 하나는 기밀성이다. 세션 ID는 그 자체로는 단순히 난수 문자열이기 때문에 어떤 의미를 갖지 않는다. 그러나 JWT는 3가지(헤더, 바디, 시그니처) 필드로 나뉘어 있으며 바디를 암호화하지 않고 URL-safe하도록 base64 인코딩이 되어있기 때문에 누구나 디코딩하여 바디를 읽을 수 있다.

본래 JWT는 웹 상에서 base64 인코딩된 JSON 객체를 활용하여 두 사용자 간 데이터를 안전하게 전송하기 위한 스펙이다. 거기에 올바른 곳에서 보낸 데이터가 맞는지 인증할 수 있도록 비밀키를 이용한 시그니처도 추가한 것이 주로 JWT라 언급되는 JWS인 것이다.

즉 JWT를 이용하여 사용자를 인증하기 위해 토큰의 바디에 개인정보(식별자, 아이디, 권한 등)를 담아둘 경우 토큰이 유출되었을 때 외부에서 인증을 도용하는 것 뿐 아니라 인증 정보까지 노출될 수 있다는 단점이 있다. 그에 비해 세션 ID는 opaque한 값이기 때문에 이 값만 가지고 어떤 정보를 추출할 수는 없다.

JWT의 크기, 속도

부수적인 문제로는 JWT 자체의 크기(문자열의 길이)와 그 연산 비용이 있다. 알고리즘 종류와 공개키, 바디에 담긴 데이터, 시그니처 등을 포함하는 JWT는 필연적으로 대부분의 세션 ID보다 길어지게 된다. 거기에 JWT 자체가 유효한 토큰이더라도 결국 해당 토큰의 유효성을 확인하기 위해서는 일련의 계산 과정(디코딩, 해싱)이 필요하며 이는 필연적으로 세션 ID보다 더 많은 자원을 소모하게 된다.

JWT의 적절한 사용

JWT는 IETF 문서에서도 언급했듯이 두 사용자(서버, 클라이언트 등) 간 인증된 무결한 데이터를 주고받기 위한 수단이다. 그러므로 마이크로서비스 환경에서 서비스 간 통신(명령 전송 등)에 짧은 유효기간의 일회용 인증 토큰으로 활용할 수 있다.

비슷한 이유로 OAuth 인증 과정에서도 JWT를 활용하는데 이 때도 짧은 유효기간동안 새로운 토큰을 발급받기 위한 토큰으로만 활용한다.

세션의 확장성

언급했듯이 세션은 기본적으로 웹 서버에 저장되기 때문에 로드 밸런서와 스케일 아웃을 통해 확장되는 애플리케이션에서 그대로 사용하기에는 제약이 있다. 그러나 Sticky Session이나 세션 클러스터링, 캐시 저장소(Redis/Memcached) 활용 등 다양한 방법으로 이를 극복할 방법 역시 많다.

토큰만 가지고 인증할 수 있기 때문에 별도로 데이터베이스에 접근할 필요가 없다는 장점은 토큰을 만료시킨다는 요구사항 때문에 사실상 실현이 불가능하다. 거기에 세션 ID의 단점으로 여겨졌던 사용자 데이터베이스 조회 과정은 최근 NoSQL의 키-값 데이터베이스를 활용하면 극복할 수 있는 부분이기 때문에 JWT나 세션이나 큰 차이가 없고 더욱 JWT의 장점이 퇴색된다.

중요한 것은 세션으로도 충분히 확장성을 얻을 수 있다는 것이다.

결론

결론은 JWT를 세션 용도로 사용하지 않는 편이 바람직하다는 것이다. 물론 사용해도 애플리케이션은 구현할 수 있지만 보안적인 측면에서 볼 때 좀 더 오래 사용되고 검증된 세션을 활용하는 편이 좋을 것이다.

그래서 이전에 개발하던 SimpleTodoList를 바꾸거나 별도의 애플리케이션을 만들어서 세션 기반으로 인증하는 분산 애플리케이션 프로젝트를 진행해보고자 한다. 그러면서 Spring Session 프로젝트도 활용해보면 좋은 경험이 될 것 같다.

참고

Authentication: JWT usage vs session
Why JWTs Suck as Session Tokens
JSON Web Token (JWT)
Stop using JWT for sessions

profile
YUKI.N > READY?

4개의 댓글

comment-user-thumbnail
2022년 7월 28일

잘 읽었습니다! 고민을 공유 해주셔서 감사해요.

1개의 답글
comment-user-thumbnail
2022년 10월 8일

잘 읽었어요. 전 JWT가 무조건 더 좋을 거라고만 생각했네요 ㅎㅎ;; 많이 배웠어요

1개의 답글