1. 도입 배경
프로젝트에서 인증 및 권한 관리 시스템을 설계하면서 가장 중요하게 고려했던 요소는 보안의 신뢰성이었습니다.
특히 병원 시스템은 일반 서비스보다 훨씬 더 엄격한 보안 기준을 요구합니다.
환자 개인정보와 의료 데이터를 다루기 때문에 인증 체계의 안정성과 확장성이 매우 중요했습니다.
초기 단계에서는 자체 인증 시스템을 직접 구현하는 방안도 검토하였습니다.
하지만 인증 시스템은 단순 로그인 기능을 넘어 세션 관리, 토큰 발급, 권한 제어, 보안 취약점 대응 등 매우 복잡한 영역을 포함합니다.
특히 OAuth2, OpenID Connect, SSO 같은 최신 인증 표준까지 안정적으로 지원해야 했기 때문에 검증된 인증 솔루션을 사용하는 방향으로 결정하게 되었습니다.
그 과정에서 선택한 솔루션이 바로 Keycloak이었습니다.
Keycloak은 Red Hat 기반 오픈소스 IAM(Identity and Access Management) 솔루션으로, OAuth2, OpenID Connect, SAML 등을 기본 지원합니다.
무엇보다 전 세계적으로 안정성이 검증된 플랫폼이라는 점이 매우 큰 장점이었습니다.
하지만 실제 프로젝트를 진행하면서 단순 기본 설정만으로는 해결할 수 없는 문제들이 발생하기 시작했습니다.
특히 프로젝트에서는 단순 사용자 인증만 필요한 것이 아니라 다음과 같은 복잡한 요구사항이 존재했습니다.
- 멀티 테넌시 기반 권한 구조
- 병원별 사용자 분리
- 세분화된 RBAC(Role Based Access Control)
- 외부 병원 데이터 기반 사용자 검증
- 실시간 신원 확인 절차
- 내부 서비스 비즈니스 로직 연동
기본 Keycloak 구조는 강력한 기능을 제공하지만, 내부 회원 관리 중심 구조에 가까웠습니다.
반면 프로젝트에서는 단순히 Keycloak 내부에 사용자를 저장하는 수준이 아니라, 서비스 비즈니스 흐름 안에서 사용자 데이터를 유기적으로 제어해야 했습니다.
특히 병원 프로젝트 특성상 아무 사용자나 회원가입을 수행할 수 없었습니다.
실제 해당 병원의 환자이거나 직원으로 확인된 사용자만 접근을 허용해야 했기 때문입니다.
즉, 회원가입 과정에서 외부 병원 데이터와 실시간으로 사용자 정보를 대조하는 특수한 검증 로직이 반드시 필요했습니다.
이러한 요구사항을 해결하기 위해 Keycloak 내부 인증 파이프라인 자체를 확장하는 방식이 필요했고, 결국 SPI(Service Provider Interface)를 활용한 커스터마이징 구조를 도입하게 되었습니다.
2. SPI(Service Provider Interface)란 무엇인가
SPI(Service Provider Interface)는 프레임워크 기능을 외부에서 확장할 수 있도록 설계된 인터페이스 구조를 의미합니다.
일반적으로 우리가 많이 사용하는 API(Application Programming Interface)는 개발자가 외부 시스템의 기능을 호출하기 위한 인터페이스에 가깝습니다.
반면 SPI는 개념이 반대입니다.
프레임워크가 미리 정의한 규칙과 인터페이스를 기반으로 개발자가 직접 구현체를 작성하면, 엔진이 이를 자동으로 찾아 실행하는 방식입니다.
즉, SPI는 “플러그인 확장 구조”에 가깝다고 볼 수 있습니다.
Keycloak은 이러한 SPI 기반 아키텍처를 중심으로 설계되어 있습니다.
덕분에 개발자는 Keycloak 핵심 소스 코드를 직접 수정하지 않고도 인증 흐름을 자유롭게 확장할 수 있습니다.
예를 들어 다음과 같은 기능들을 SPI 형태로 확장할 수 있습니다.
- 사용자 저장소(User Storage)
- 인증(Authentication)
- 이벤트(Event Listener)
- 프로토콜 매핑(Protocol Mapper)
- 패스워드 정책(Password Policy)
- 사용자 등록 흐름
프로젝트에서는 특히 Authentication SPI를 활용하였습니다. 구현 방식은 비교적 명확합니다.
Keycloak이 제공하는 인터페이스 규격에 맞춰 커스텀 클래스를 구현한 뒤, 이를 JAR 파일 형태로 빌드하여 Keycloak 서버에 배포합니다.
이후 Keycloak 서버가 시작되면 내부적으로 SPI 구현체를 자동 스캔하여 새로운 인증 모듈로 인식하게 됩니다.
즉, 개발자가 만든 로직이 마치 Keycloak 기본 기능처럼 동작하게 되는 것입니다.
이 구조의 가장 큰 장점은 엔진 내부를 수정하지 않는다는 점입니다.
즉, Keycloak 버전 업그레이드 시에도 커스터마이징 코드와 엔진 코드를 분리해 유지할 수 있습니다.
프로젝트에서는 이러한 구조 덕분에 병원 시스템만의 특수한 인증 요구사항을 표준 인증 플랫폼 안에 자연스럽게 통합할 수 있었습니다.
3. 인증 플로우(Authentication Flow)의 메커니즘
Keycloak의 인증 시스템은 Authentication Flow라는 파이프라인 구조 위에서 동작합니다.
사용자가 로그인이나 회원가입을 수행하면 Keycloak은 미리 정의된 인증 단계들을 순서대로 실행합니다.
예를 들어 일반적인 로그인 흐름은 다음과 같은 단계로 구성될 수 있습니다.
- 사용자 정보 입력
- 비밀번호 검증
- OTP 인증
- 추가 인증 절차
- 토큰 발급
각 단계는 Execution이라는 단위로 관리됩니다.
프로젝트에서는 이 Authentication Flow 중간에 커스텀 검증 단계를 삽입하는 방식으로 구현을 진행하였습니다.
먼저 SPI 기반 인증 클래스를 구현하고 JAR 형태로 빌드하여 Keycloak 서버에 배포하였습니다.
배포 이후 관리자 콘솔에서는 해당 커스텀 로직이 새로운 인증 Execution으로 인식되었습니다.
이후 관리자 화면에서 기존 인증 플로우 안에 직접 삽입할 수 있었습니다.
즉, 표준 로그인 과정 중간에 병원 검증 로직을 자연스럽게 연결할 수 있었던 것입니다.
실제 사용자가 로그인 또는 회원가입을 시도하면 Keycloak은 설정된 순서에 따라 각 인증 단계를 실행합니다. 그리고 커스텀 단계가 호출되면 AuthenticationFlowContext 객체를 전달합니다. 이 객체는 인증 흐름 전체 상태를 제어할 수 있는 매우 중요한 도구였습니다.
프로젝트에서는 AuthenticationFlowContext를 활용하여 다음 작업들을 수행하였습니다.
- 사용자 입력 데이터 조회
- 병원 DB 연동
- 환자 정보 검증
- 직원 정보 검증
- 테넌트 권한 검증
- 인증 성공/실패 처리
예를 들어 사용자가 입력한 병원 식별 번호와 개인정보를 외부 병원 시스템과 실시간으로 비교하여 실제 환자 또는 직원 여부를 확인하였습니다.
검증 성공 시에는 context.success()를 호출하여 다음 인증 단계로 진행시켰고, 실패 시에는 오류 메시지를 반환하여 인증을 중단하였습니다.
이러한 구조를 통해 Keycloak 기본 인증 흐름을 유지하면서도 프로젝트에 특화된 비즈니스 로직을 유연하게 삽입할 수 있었습니다.
4. 기술적 난제: 분산 환경에서의 데이터 정합성 문제
실제 구현 과정에서 가장 어려웠던 문제 중 하나는 분산 환경에서의 데이터 정합성이었습니다.
특히 병원 사용자 식별 과정에서 사용하는 DI(Duplication Information) 값을 여러 인증 단계 사이에서 안전하게 유지해야 했습니다.
초기에는 Keycloak 내부에서 제공하는 AuthNote 기능을 활용하려고 했습니다.
AuthNote는 인증 세션 내부에서 단계 간 데이터를 공유하기 위한 임시 저장 공간입니다.
단일 서버 환경에서는 비교적 안정적으로 동작하였습니다. 하지만 실제 운영 환경은 다중 서버 기반 클러스터 구조였습니다.
문제는 외부 보안 모듈 인증 과정에서 발생했습니다.
사용자는 인증 과정 중 외부 인증 서버로 리디렉션되었다가 다시 Keycloak으로 복귀하게 되는데, 이 과정에서 세션이 다른 노드로 이동하는 경우가 발생했습니다.
그 결과 다음과 같은 문제가 반복적으로 발생하였습니다.
- AuthNote 데이터 유실
- 세션 정합성 불일치
- 인증 상태 초기화
- DI 값 조회 실패
- 사용자 인증 중단
처음에는 브라우저 쿠키 기반 저장 방식을 검토하였습니다.
또한 Keycloak 세션 설정 변경이나 Sticky Session 구조도 테스트해보았습니다.
하지만 이러한 방식들은 구조적으로 완전한 해결책이 되기 어려웠습니다.
특히 병원 인증 시스템은 안정성과 신뢰성이 매우 중요했기 때문에 일부 상황에서라도 인증 실패 가능성이 존재하면 안 되었습니다.
결국 문제의 핵심은 “분산 서버 환경에서도 안정적으로 상태 데이터를 공유할 수 있는 구조”를 만드는 것이었습니다.
이 문제를 해결하기 위해 Keycloak 내부 캐시 구조를 직접 활용하는 방향으로 접근하게 되었습니다.
5. 문제 해결: Infinispan 기반 분산 캐시 설계
분산 환경에서 발생하는 인증 세션 유실 문제를 해결하기 위해 선택한 기술은 Infinispan이었습니다.
Infinispan은 Java 기반 오픈소스 분산 인메모리 데이터 그리드(Data Grid) 솔루션입니다.
흥미로운 점은 Keycloak 자체가 내부적으로 이미 Infinispan을 사용하고 있다는 것이었습니다.
즉, 별도의 Redis나 외부 저장소를 추가하지 않아도 Keycloak 내부 캐시 구조를 그대로 활용할 수 있었습니다.
이는 시스템 복잡도를 크게 줄여주는 장점이 있었습니다.
프로젝트에서는 KeycloakSession과 InfinispanConnectionProvider를 활용해 커스텀 캐시 접근 구조를 구현하였습니다.
구현 과정은 크게 세 단계로 구성되었습니다.
첫 번째는 캐시 인스턴스 확보였습니다.
Keycloak이 관리하는 Cache Manager에 접근하여 사용자 정의 데이터를 저장할 전용 캐시 공간을 생성하였습니다.
두 번째는 상태 값(State Key) 기반 데이터 저장 구조였습니다.
인증 프로세스 시작 시 고유한 상태 값(State Key)를 생성하고, 이를 키 값으로 사용하였습니다.
그리고 병원 인증 과정에서 필요한 DI 값을 Value로 저장하였습니다.
즉, 인증 흐름 중간에서는 실제 DI 값을 직접 전달하는 것이 아니라 상태 값(State Key)만 전달하도록 구성하였습니다.
세 번째는 TTL(Time To Live) 설정이었습니다.
인증 과정 데이터는 일시적으로만 필요하기 때문에 메모리 효율성을 고려해야 했습니다.
따라서 캐시에 저장된 데이터는 일정 시간이 지나면 자동 삭제되도록 TTL을 설정하였습니다.
이 구조 덕분에 사용자가 외부 인증 이후 다시 Keycloak으로 돌아왔을 때 상태 값(State Key) 만으로 DI 값을 안전하게 복구할 수 있었습니다.
무엇보다 중요한 점은 분산 서버 환경에서도 동일한 데이터를 안정적으로 공유할 수 있었다는 점이었습니다.
즉, 인증 과정 중간에 서버 노드가 변경되더라도 세션 유실 없이 인증 흐름을 이어갈 수 있었습니다.
결과적으로 Infinispan 기반 구조를 통해 병원 프로젝트에서 요구하는 높은 수준의 인증 안정성과 데이터 정합성을 만족시킬 수 있었습니다.
6. 마치며
Keycloak SPI 기반 커스터마이징은 단순 플러그인 개발 이상의 경험이었습니다.
처음에는 단순히 인증 기능 몇 가지를 추가하는 수준으로 생각했지만, 실제로는 Keycloak 엔진 내부 구조와 인증 흐름 메커니즘 자체를 깊이 이해해야 했습니다.
특히 Authentication Flow, AuthenticationFlowContext, SPI 구조, Infinispan 캐시 아키텍처 등은 단순 설정만으로는 이해하기 어려운 영역이었습니다.
하지만 이러한 구조를 직접 분석하고 문제를 해결해 나가면서 인증 플랫폼 자체에 대한 이해도가 크게 높아졌습니다.
무엇보다 의미 있었던 부분은 “우리 서비스만의 복잡한 요구사항”을 표준 인증 플랫폼 안에 안정적으로 녹여냈다는 점이었습니다.
특히 병원 프로젝트는 일반 서비스보다 훨씬 더 엄격한 인증과 보안 요구사항을 가지고 있었습니다.
단순 로그인 기능이 아니라 실제 병원 사용자 여부를 검증하고, 멀티 테넌시 구조 안에서 권한을 세분화하며, 분산 환경에서도 안정적인 인증 경험을 제공해야 했습니다.
이러한 과정 속에서 단순 기능 구현을 넘어 시스템 설계와 데이터 정합성, 인증 안정성까지 종합적으로 고민할 수 있었습니다.
결과적으로 이번 프로젝트는 기술적으로도 매우 도전적인 경험이었으며, 개발자로서 한 단계 더 성장할 수 있었던 값진 프로젝트였습니다.
앞으로도 인증 및 권한 관리 시스템을 설계할 때 이번 경험은 매우 중요한 기준점이 될 것이라 생각합니다.
joshua