학습일지

확장에 유리한 아키텍처? 그러면, 인증정보는 어떻게 관리할까?

hj.choi 2021. 11. 29. 00:46

저번에 작성한 문서에 따르면, 확장에 유리한 아키텍처로 3-Tier를 선정했었다.

CRUD drawio (1)

그렇가면, 어떤 계층이 가장 확장하기 쉬울까? 약간의 논란의 여지가 있겠지만, 가장 확장이 쉬우며, 확장을 필요로 하는 계층은 Application Layer라고 할 수 있겠다. 이유가 궁금하다면, 이 계층의 주 역할이 트랜잭션 처리라는 점에 주목하면, 어느정도 설명이 될 것 같다.

수 많은 클라이언트가 첫 번째 페이지의 상품목록을 조회하는 /api/item 이라는 GET 요청을 동시에 했을 때의 상황을 가정해보자.

애플리케이션 서버의 환경에 따라 다르겠지만, Spring의 경우, RDBMS로 Data Layer에 접근할 때, JDBC를 사용한다. 설정환경에 다르겠지만, 보편적으로는 Connection pool을 거쳐, 사용가능한 connection을 획득하여, 트랜잭션을 처리하고, 클라이언트에 응답을 하는데.. 이러한 행위를 초당 몇 개의 트랜잭션을 처리하느냐에 따라, 운영되는 서버의 성능을 가늠할 수 있게된다. 여기서 초당 처리할 수 있는 트랜잭션의 수TPS(Transaction Per Second)라고 한다.

여기서 TPS를 늘리는 방법으로 Connection Pool에 connection을 미리 더 많이 만드는 방법이 있겠지만, 무작정 늘려봐야 결국 CPU에 지나친 오버헤드가 발생하여, 오히려 병목현상이 일어나, 결과적으로 TPS 성능을 더 낮출 수 있다. 이러한 상황에서 TPS를 늘리기 위해서 할 수 있는 방법은 애플리케이션 서버 자체의 하드웨어 성능을 높이거나, 현재 하드웨어로 소화할 수 있는 TPS 성능을 맞추고 같은 성능의 하드웨어를 가진 서버의 수를 늘리는 것이다. 이 부분을 자세히 설명하기 위해선, LoadBalancer, Scale-up, Scale-out의 개념을 설명해야 할 것 같지만, 이 부분은 나중에 따로 주제를 파서 문서로 정리하도록 하겠다.

부연 설명이 길었는데, 여기서 주장하고 싶었던 부분은 '확장을 통해 큰 효과를 볼 수 있는 계층으로 Applicaion Layer가 있다.'라는 것이다. 이러한 주장을 바탕으로 확장된 웹 애플리케션의 아키텍처를 그려보면 다음과 같을 것이다.

CRUD drawio (2)

그런데, 여기서 한가지 문제가 있다. 만약, 3개의 Application Layer 노드 중에서 하나가 망가지면, 그 노드에 있던 사용자는 어떻게 될까? 크게 Session과 JWT를 비교하여, 생길 수 있는 문제와 해결법을 탐구해보자. 인증과 인증 방식에 대해 잘 모르는 사람은 이 문서를 읽어보자.

Session 기반

일반적인 세션의 경우, Application 노드 하나가 망가질 경우, 해당 사용자의 인증정보를 잃어버릴 확률이 크다. 따라서, 그 망가진 노드로 요청을 했던 사용자들은 다시 로그인을 해야한다. 특히, e-commerce의 경우 다음과 같은 장애를 겪을 수 있겠다.

  • 판매자가 상품 정보를 입력하고, 등록하기를 눌렀는데, 다시 로그인을 하라면서, 그동안 힘들게 입력했던 상품 정보가 전부 날라갔다. 그 판매자는 실망하여, 다른 플랫폼을 찾아 상품을 판매하게 된다.
  • 소비자가 상품을 고르고 결제 정보를 열심히 입력하고 구매하기를 눌렀는데, 다시 로그인 하라는 메세지를 받는다. 불행하게도 구매자는 다시 결제 정보를 입력해야하는 상황에 극심한 스트래스를 받고, 다른 e-commerce 플랫폼으로 갈아탄다.

세션으로 인해 Application 노드가 망가지는 경우는 서버에 session을 지나치게 많이 저장하였을 때, 발생한다고 볼 수 있다. 왜냐면, HTTP는 무상태 프로토콜이라, 기본적으로는 어떠한 정보도 저장하지 않는다. 즉, 기억력이 없다. 따라서, 서버가 인증된 사용자를 구분하기 위해선, session을 만들고, session은 클라이언트로부터 요청된 session-id를 참조하여 응답한다. 그런데, session은 서버의 메모리를 차지하기 때문에, 너무 많이 몰리면, 메모리 부족이 발생하고 이는, 서버 장애로 이어진다. 서버 장애는 곧 session 정보의 유실로 이어진다.

그러면 해결법은?

크게 두 가지로 해결해볼 수 있을 것이다.

  • Sticky Connection
    • 동일한 사용자가 발생시킨 요청은 동일한 WAS에서 처리됨을 보장하는 로직.
    • 그러나 재수 없으면, 특정 애플리케이션 서버에만 요청이 몰려 장애가 발생할 수 있다.
  • Session Cluster
    • 모든 서버들이 Session을 공유한다.
    • 보통, Session 서버를 따로 운영하여, Session 정보만 담는 데이터베이스를 추가로 운영한다.
    • 전부 공유하는 기능이 매력적이지만, 일단, 관리해야하는 포인트가 늘어난다. (일이 많아진다.)
    • 또한 이 session cluster가 망가지면, 대규모 장애가 발생한다.

JWT

우선 JWT는 Stateless 철학에 충실한 인증 방식이라 할 수 있겠다. JSON 객체를 사용하여, 필요한 정보를 자체적으로 지니고 있기 때문이다. 또한 그 JSON의 정보들이 검증됐다는 것을 증명(Signature)하는 과정이 포함되었으며, HTTP 헤더를 통해 클라이언트에서 서버로 바로 인증 정보를 전송하기 때문에, 수신 측은 별도의 데이터 레이어의 개입이 필요없이 바로 유효성 검사 및 사용자 식별이 가능하다. (쉽게 말해서, 인증 및 사용자 식별을 하는데, 세션 서버가 필요 없다는 뜻이다.)

즉, 세션과는 달리 인증 정보를 공유하지 않아도, self-contained된 HTTP 요청을 통해 인증 및 사용자 식별이 가능하기 때문에, 애플리케이션 서버가 분산된 횟수에 상관없이 인증 정보를 stateless하게 공유할 수 있다. 그러나 이 방식은 경우에 따라서, 보안상으로 큰 문제가 발생할 수 있는데, 이 방식은 여러 문서에서도 언급했지만, 구체적으로 다룬 문서로는 이게 있을 것 같다.

마무리

저번에 작성한 문서를 통해, 보편적인 3-tier 웹 아키텍쳐에 대해 정리를 하고, 이번 문서를 통해, Application 서버를 확장하였을 때, 사용자 인증정보를 저장하면서 생길 수 있는 문제와 해결점을 크게 Session과 JWT의 관점에서 정리 해보았다. 정리해보자면, Session은 따로 서버를 관리해야함으로서, 유지보수 작업의 포인트가 많아지지만, 좀더 강력한 보안을 적용할 수 있는 여지가 있다. JWT의 경우, HTTP 요청 자체에 인증 정보를 self-contained 함으로서, 별도의 세션 서버를 관리할 필요없이 여러 애플리케이션 서버가 인증정보를 공유하지 않아도 되는 편리함을 확보할 수 있지만, 탈취 당할경우, 대처할 수 있는 수단이 없다.

결론은, 어느 한쪽만을 고집할게 아니라, 두 가지의 특성을 파악하고, 서로의 trade off를 고려하여, 상황에 맞게 선택하면 될 것 같다. 또한 두 가지 방식을 섞어서, 응용을 해볼 수도 있다.