MSA는 하나의 애플리케이션을 여러 개의 독립적인 서비스로 분리하여 개발, 배포, 유지보수를 용이하게 하는 소프트웨어 아키텍처이다. 모놀리틱 아키텍처는 하나의 애플리케이션이 모든 비즈니스 로직을 담당하고 있었다면, MSA에서는 특정 비즈니스 기능을 수행하는 각 서비스가 독립적으로 배포되고 확장될 수 있다.
하나의 애플리케이션에는 여러 비즈니스가 얽혀있다. 예를 들어, 사용자가 상품을 주문한다고 하면 User, Product, Order 도메인과 관련된 비즈니스 로직들이 정확하게 연결되어 동작해야 한다. 그렇기 때문에 MSA에서는 각 마이크로 서비스 간 통신을 구현할 필요가 있으며 이는 주로 HTTP/HTTPS 혹은 메시지 큐 등을 통해 이루어진다.
Spring Cloud를 활용해 서비스 디스커버리, 로드 밸런싱, 서킷 브레이커, 게이트웨이 등을 간단하게 구현해보다가 문득 든 생각은, '각 서비스가 독립적으로 나뉘어져 있으면 사용자에 대한 인증이나 인가 처리는 어떻게 해야하는 거지?'였다. 사용자에 대한 인증과 인가는 MSA를 구성하는 모든 마이크로 서비스에서 공통적으로 필요로 하는 부분이었다.
1. 인증 (Authentication) vs 인가 (Authorization)
우선, MSA 환경에서의 인증/인가 처리 방식에 대해 논하기 전에 인증과 인가의 차이점부터 알아보자. Spring Security와 JWT를 기준으로 설명할 것이다.
인증
인증은 사용자의 신원을 입증하는 것으로 쉽게 말하면 '로그인'이다. 사용자가 서비스를 이용하기 위해 아이디와 비밀번호를 입력하면 이를 기반으로 회원 정보를 확인하고, Access Token을 발급하는 과정이다.
인가
인가는 로그인한 사용자가 특정 서비스를 사용할 수 있음을 '허가'하는 것이다. 로그인한 사용자가 Access Token과 함께 서비스 쪽에 요청을 보내면, 사용자의 Access Token이 유효한 지 검증하고 사용자의 요청에 대한 권한을 확인 및 허가하는 과정이다.
인증은 쉽게 말해 처음에 입력 받은 사용자 정보가 유효한지를 토대로 토큰을 발급하는 과정이고, 인가는 토큰을 통해 해당 사용자를 식별하고 요청에 대한 권한을 확인하는 과정이다. 이 둘의 목적은 명확하게 분리되어 있으며, 그렇다면 MSA 환경에서 MA 환경에서처럼 인증/인가를 한 서버에서 관리할 지 인증/인가를 분리해 처리할 지에 대해 생각해보아야 한다.
2. MSA에서의 인증/인가 처리 방식 비교
MSA에서는 각 마이크로서비스로 사용자 요청을 전달하기 전, Gateway 역할을 하는 서버를 구성한다. Gateway 서버는 시스템 내의 모든 마이크로서비스를 관리하는 Registry 서버를 통해 서비스 디스커버리를 수행하고, 적절한 서비스로 요청을 라우팅하는 기능을 담당한다. 또한, 사용자 요청의 최초 및 단일 진입점이라는 특성상, 인증과 인가를 처리하는 방식에 대한 다양한 고려가 필요하다. 이에 대한 몇 가지 접근 방식을 비교해본다.
2-1. Gateway에서 인증/인가를 모두 처리하는 방식
장점
- 모든 요청이 Gateway를 통과하므로 보안 및 접근 제어를 중앙에서 관리 가능
- 각 마이크로서비스는 인증/인가 로직을 몰라도 요청을 처리할 수 있음
단점
- 트래픽 증가 시 단일 장애 지점(SPOF, Single Point of Failure) 및 병목(Bottleneck) 가능성
- Gateway가 인증과 인가를 모두 담당하면 공격의 주요 대상이 될 위험 증가
2-2. 인증/인가 서버를 마이크로서비스로 분리하는 방식
장점
- 인증/인가 서버와 Gateway를 분리하여 공격 지점을 최소화할 수 있음
- 마이크로서비스별 적합한 접근 제어 정책을 개별적으로 적용할 수 있음
단점
- 아키텍처가 복잡해지고 사용자 인증/인가를 위한 추가적인 네트워크 요청 발생
- 인증 서버와 Gateway 간의 통신 방식을 명확하게 정의해야 함
2-3. 인증은 Gateway에서, 인가는 각 마이크로서비스에서 수행하는 방식
장점
- Gateway에서 인증만 담당하여 인증 서버의 부담을 줄일 수 있음
- 각 마이크로서비스에서 개별적인 인가 정책을 적용할 수 있음
단점
- 각 마이크로서비스에 JWT 시크릿 키 등의 설정이 필요하여 관리가 번거로울 수 있음
- 권한 검증 로직이 각 서비스에 중복될 가능성이 있음
2-4. 인증은 Gateway에서, 인가를 담당하는 별도 마이크로서비스를 두는 방식
장점
- 인가 로직을 중앙에서 관리하여 일관된 접근 제어 정책을 적용할 수 있음
- 개별 마이크로서비스에서 인가 로직을 처리하지 않아 코드 중복을 줄일 수 있음
단점
- 추가적인 네트워크 요청이 발생하여 응답 속도가 지연될 가능성이 있음
- 인가 서비스가 병목이 될 수 있어 확장성을 고려한 설계가 필요함
3. 그래서? 적합한 방식은?
어떤 방식을 선택할지는 서비스의 규모와 요구사항에 따라 달라질 수 있다. 어떤 방식을 선택하느냐에 따라 서비스의 복잡성이 증가할 수 있으므로, 도입 전에 트래픽, 성능, 관리 비용 등을 종합적으로 고려하는 것이 중요하다.
사실 토스에서는 별도의 서버에서 인증을 담당한다고 한다. 토스의 경우 별도의 인증/인가 서버를 구성하는 대신, passport라는 내부 토큰을 활용해 사용자 요청과 관련된 하나의 트랜잭션 내에서 각 마이크로서비스가 중복적으로 서버에 접근하는 것을 줄인다고 한다. 필요한 사용자 정보를 passport에 담아 트랜잭션 내에 전파시키는 방법이고 보안적으로 강력하다는 생각이 들지만, passport과 같은 내부 토큰을 우리 프로젝트에 적용하기에는 어려움이 있을 것 같다.
정답은 없지만, 넷플릭스에서는 Gateway에서 인증 처리를 권장하고 있으며 모든 요청이 Gateway를 통해 진입된다는 점을 고려해 Gateway에서 인증을 담당하는 것도 괜찮을 것 같다. 그러나 Gateway에 인가 로직을 구현하게 되면 트래픽이 많아질 경우 요청의 단일 진입점임과 동시에 모든 마이크로서비스에서의 인가 요청을 담당해야 하는 이유로 단일 장애 지점이 될 여지가 있다. 그래서 인가 서버는 별도의 마이크로서비스로 분리하는 2-4번 방식이 좋아보인다.
4. 인가를 위한 별도의 마이크로서비스를 구성할 시 병목이 될 가능성
이제 사용자의 인증과 인가를 분리했으니, 인가에 대해 자세히 생각해보아야 한다. 하나의 사용자의 요청이 들어온다고 하면, MA에서는 한 번의 인가 처리만 진행되면 된다. 그러나 MSA에서는 각 마이크로서비스가 동작하기 전 계속해서 권한을 확인하고 인가 처리를 받아야 한다. 장점은 확실하다.
장점
- 권한 변경 즉시 반영
- 권한 변경 시 즉시 반영되므로, 권한이 변경되는 경우 실시간으로 반영할 수 있다.
- 사용자가 로그인 후 권한이 변경되면, 그 즉시 새 권한으로 인가를 받게 된다.
- 중앙집중식 관리
- 모든 서비스가 Auth Server를 통해 인가를 하므로, 모든 권한 로직을 한 곳에서 관리할 수 있어 보안이 강화된다.
- 서비스가 많아져도 권한 관리를 분산할 필요 없이 Auth Server 하나에서 관리하기 때문에 유지보수나 보안적인 측면에서 유리하다.
- 블랙리스트나 세션 관리 불필요
- JWT 블랙리스트나 세션을 관리할 필요 없이 권한을 실시간으로 체크하므로, 추가적인 관리 부담이 없다.
그러나, 요청에 포함된 마이크로서비스 개수만큼의 인가 요청이 발생해 인가 서버 자체가 다시 병목이 될 가능성이 생긴다.
단점
- 성능 저하
- 각 서비스가 매 요청마다 Auth Server를 호출해야 하므로, 성능에 부담이 있을 수 있다.
- 특히 높은 트래픽이 발생하는 시스템에서는 Auth Server의 부하가 증가할 수 있고, 지연 시간이 발생할 수 있다.
- Auth Server의 장애 발생 가능성
- Auth Server가 다운되면 모든 서비스에 영향을 미칠 수 있다.
- 장애 복구를 위한 가용성 고려가 필요하다.
모든 마이크로서비스 작업에서 Auth Server를 호출하면 성능 문제가 생길 수 있기 때문에 적절한 트레이드오프를 생각해보게 되었다.
5. 성능을 고려한 인증/인가 방식 고도화
사용자 정보 조회 시, 마이크로서비스간 네트워크 비용이 발생한다. 그렇다면 성능을 최적화할 수 있는 방법을 생각해야 한다. 우선 사용자 권한을 기준으로 크게 두가지를 생각해보았다.
- 권한 정보 Redis 캐싱
- 사용자의 권한 정보를 Auth Server에서 조회하고, 이를 Redis에 캐싱하여 반복적인 조회를 피한다.
- 권한 정보는 자주 변경되지 않기 때문에 일정 시간 동안 Redis에서 데이터를 조회한 후, 권한이 변경되면 Redis를 갱신하는 방식으로 성능을 최적화할 수 있다.
- 짧은 캐시 만료 시간 설정과 주기적인 캐시 갱신
- 권한 정보는 짧은 TTL로 설정하고, 주기적으로 Auth Server에서 권한을 갱신한다.
- 최신 권한을 빠르게 반영하면서도 불필요한 호출을 줄일 수 있다.
정답은 없지만, 조회가 잦은 사용자 테이블 권한 조회와 관련된 내용을 Redis에 캐싱하는 방법을 생각해보았다. 적절한 방법을 섞어서 사용하면 되겠지만, 중요한 정보에 대한 권한인 master의 경우엔 Redis가 아닌 테이블을 확인하도록 구성하는 것도 보안 측면에서 나은 선택이 될 수 있을 것 같다.
사실 권한에 대한 것뿐만 아니라 사용자 정보도 캐싱이 가능할 것 같다. 그러나 보안적으로 이게 괜찮은 방식인 지에 대해서는 조금 더 생각해보아야 할 지점이 있어보인다.
'Infrastructure' 카테고리의 다른 글
Windows11에서 Ubuntu 및 Docker 환경 구성하기 (0) | 2025.02.28 |
---|---|
Jenkins로 GitHub CI/CD Pipeline 구축하기 (0) | 2025.02.26 |
Docker out of Docker, 도커 컨테이너 안에서 도커 사용하기 (2) | 2025.02.24 |
Jenkins로 GitHub CI 구축하기 (0) | 2025.02.21 |
Git flow에서 CI/CD는 어떤 브랜치에 구축하는 것이 좋을까? (0) | 2025.02.20 |