Search

Redis 를 이용한 인증 시스템 구축

태그
아키텍처
보안
작성 상태
작성 완료
작성일
2023/07/15
참고 링크
참고 링크 2

시스템 요구사항

1.
인증 정보의 생성은 REST API 서버에서 담당한다.
2.
같은 인증 정보로 분리된 REST API 서버와 웹소켓 서버에서 사용자를 인증할 수 있어야 한다.
3.
사용자는 시스템이 지원하는 클라이언트 플랫폼마다 하나의 기기에서만 로그인 할 수 있다.
a.
윈도우, 안드로이드, IOS, Mac 을 지원한다.
b.
같은 플랫폼의 다른 기기에서 중복으로 로그인을 시도할 경우 사용자의 선택에 따라 기존에 로그인 된 기기를 로그아웃하고, 로그인 시도하는 기기에서 로그인을 할 수 있다.

생각의 흐름

인증 토큰? JWT? 세션?

그림 1. 기본적인 인증 구조
처음 생각한 인증 구조는 위와 같았다. REST API로 로그인을 하면 일종의 인증 토큰이 발급되고 이를 인증이 필요한 API 요청을 할 때마다 헤더에 실어 보내는 구조다. 일반적인 상황이라면 JWT 를 사용하면 간단하지만, 이 조건을 충족하려면 인증 정보를 어떤 방식으로든 서버에 저장해야 했다. 그래야 기존의 인증 토큰으로 인증을 시도할 때 거부할 수 있기 때문이다. 이를 위한 가장 간단한 방법은 전통적인 세션을 사용하는 방식이다.
그런데, 두 API서버가 WAS 단위로 분리되어있기 때문에 같은 세션을 사용하기 위해서는 추가 설정이 필요하다.

분산 환경에서 세션 문제 해결하는 방법

여러 서버가 같은 세션을 가져야 하는 상황이 분산 환경에서 많이 발생할 것이라 생각하여 분산 환경에서 세션 문제를 어떻게 처리하는지 알아보았다.
분산 환경에서 위와 같은 세션 문제를 해결하기 위한 방법으로 Sticky SessionSession Clustering 을 들 수 있다. 전자의 경우 세션아이디를 이용해 로드밸런싱을 하는 것으로, 모든 요청이 하나의 서버로 전달되는 것을 보장하기 때문에 분산 서버들끼리 같은 세션을 공유할 필요가 없다. 후자의 경우 세션 정보를 공유하는 방식으로 세션을 생성할 때 분산 서버들에 모두 동일한 세션을 생성하거나, 하나의 공통된 세션 저장소를 공유하는 방식이다. 우리가 해결해야 하는 상황과 비슷한 것이 Session Clustering 이다. 자세히 알아볼 필요가 있었다.

Session Clustering

기본적으로 여러 서버가 같은 세션을 공유하는 가장 간단한 방법은 중앙화된 세션 저장소를 만들고 모든 서버가 이 중앙 세션 저장소에 접근하여 세션 정보를 확인하는 것이다. 이 방법은 모든 트래픽이 결국 중앙의 세션 저장소에 집중되므로 세션 저장소의 부하가 문제가 될 수 있다.
다른 방법으로는 각각의 서버가 독립된 세션 저장소를 가지되, 세션 정보의 추가, 변경, 삭제가 발생하였을때 일종의 이벤트가 발생하게 하고 각각의 서버(혹은 세션 저장소)가 이 이벤트를 처리해 반영하는 방법이 있다. 부하가 분산된다는 장점이 있겠지만 당연히 구현이 쉽지 않을 것이다.
서비스 초기에 트래픽이 많을리 없으니 일단 중앙의 세션 저장소를 두는 방식을 선택했다.
일반적인 Spring session을 사용하는 환경에서는 패키지 의존 추가와 간단한 설정을 추가하면 세션 정보를 redis에 저장할 수 있게 된다.
일반적인 REST API서버 분산 환경에서의 설정
그러나 우리 서비스는 세션을 공유해야 하는 대상이 REST API 서버와 웹소켓 서버기 때문에 세션의 생성, redis 에 세션 정보를 저장하는 것, redis에서 세션 정보를 조회하는 것, redis의 세션 정보를 갱신하는 것 을 직접 구현해야 했다.

직접 구현한 Session 알고리즘

요구사항을 다시 검토해보면,
1.
인증 정보의 생성은 REST API 서버에서 담당한다.
2.
같은 인증 정보로 분리된 REST API 서버와 웹소켓 서버에서 사용자를 인증할 수 있어야 한다.
3.
사용자는 시스템이 지원하는 클라이언트 플랫폼마다 하나의 기기에서만 로그인 할 수 있다.
a.
윈도우, 안드로이드, IOS, Mac 을 지원한다.
b.
같은 플랫폼의 다른 기기에서 중복으로 로그인을 시도할 경우 사용자의 선택에 따라 기존에 로그인 된 기기를 로그아웃하고, 로그인 시도하는 기기에서 로그인을 할 수 있다.
세션 아이디를 통해 사용자의 아이디를 식별할 수 있어야 하고, 사용자 아이디와 플랫폼을 통해 이미 로그인되어있는지 식별할 수 있어야 한다.” 라는 사실을 알아낼 수 있다.
세션아이디 - {사용자아이디, 플랫폼} 를 통해 세션아이디로 사용자와 플랫폼을 구할 수 있다. {사용자아이디,플랫폼} - 세션아이디 를 통해 중복 로그인을 체크할 수 있다.
또한, 중복 로그인 시도가 발생할 경우 새로운 기기에서의 로그인 여부를 사용자에게 다시 물어보는 로직이 있으므로, 일반적인 로그인 API 외에 강제 로그인 API가 추가로 필요했다. 강제 로그인 API는 평소에는 사용되면 안되고 중복 로그인이 발생한 경우에만 사용되어야 한다. 따라서 강제 로그인이 활성화되었는지 알 수 있어야 한다.
{사용자아이디, 플랫폼} - {아무 값} 을 통해 강제 로그인이 활성화 되었는지 식별할 수 있다. null이 아니면 강제로그인이 활성화 된 것으로 판단한다.
이를 구현한 코드는 생략한다. RedisTemplate 을 사용하면 간단히 구현할 수 있다.
이렇게 만들어진 시스템의 설계는 아래와 같다.
그림 2. 강제 로그인을 하는 상황의 시스템 동작 다이어그램
그림 3. 강제 로그인을 하지 않는 상황의 시스템 동작 다이어그램
그림 4. 새로 로그인하는 상황의 시스템 동작 다이어그램

새로운 고민거리

만약 추가 정보를 저장해야 할 상황이 생기거나, 새로운 종류의 서버가 추가된다면 그때마다 인증 코드를 복사해서 붙여넣어야 한다.
개발자라면 누구나 아는 위험한 상황이다. 이를 해결하기 위해서는 인증 작업이 지금처럼 분산되어 있는 것이 아니라 한 곳으로 모아져야 한다.

SSO(Single Sign-On, 통합 인증)

한 번의 인증 과정으로 여러 컴퓨터 상의 자원을 이용 가능하게 하는 인증 기능. - 위키 백과
사용자의 요청이 중앙의 인증 서버를 통과하여 실제 서비스 제공 서버로 전달되는 인증 대행 모델과, 본 요청 전에 인증 서비스로 요청을 보내고, 인증 서비스가 발급한 일회용 인증 토큰을 각각의 실제 서비스 제공 서버로 본 요청을 보내는 인증 정보 전달 모델이 있다.
인증 대행 모델의 구조는 API 게이트웨이에 인증 기능을 추가한 것이다. 이 모델을 사용하면 인증의 통합과 함께 로그의 중앙화 역시 가능할 것으로 판단해서 도입하기로 결정했다.