- Published on
Keycloak 26 아키텍처 딥다이브 — Realm, Client, Quarkus 런타임의 이해
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- Keycloak의 역사 — WildFly에서 Quarkus로
- 도메인 모델 — Realm, Client, User, Role, Group
- Confidential vs Public Client
- Client Scope — 토큰 내용물의 조립 단위
- 인증 플로우 엔진 — Browser Flow의 내부 구조
- 데이터베이스 스키마 개요
- kc.sh — Build와 Start의 2단계 구조
- 설정 방법 — 환경변수, conf 파일, CLI
- Keycloak 26.x 신기능 정리
- 프로덕션 체크리스트
- 트러블슈팅 — 자주 만나는 문제와 원인
- 안티패턴 모음
- 마치며
- 참고 자료
들어가며
2026년 현재 Keycloak은 오픈소스 IAM(Identity and Access Management) 분야에서 사실상의 표준 위치를 차지하고 있습니다. CNCF 인큐베이팅 프로젝트로 자리 잡은 이후 릴리스 주기가 안정화되었고, 2026년 5월 기준 최신 버전은 26.6.2입니다. passkeys 기본값화, AI 에이전트 아이덴티티(non-human identity), Zero Trust 아키텍처 같은 2026년의 트렌드 속에서 Keycloak은 FAPI 2.0 Security Profile 최종 지원, JWT Authorization Grant, OAuth Client ID Metadata Document(CIMD) 실험 지원 등으로 빠르게 진화하고 있습니다.
하지만 Keycloak을 "그냥 띄워서 쓰는" 수준을 넘어 프로덕션에서 안정적으로 운영하려면 내부 아키텍처에 대한 이해가 필수입니다. 이 글에서는 다음을 다룹니다.
- WildFly에서 Quarkus로의 전환이 가져온 변화
- realm, client, user, role, group으로 구성되는 도메인 모델
- confidential vs public client와 client scope의 동작 원리
- 인증 플로우(browser flow) 엔진의 내부 구조
- 데이터베이스 스키마 개요
- kc.sh의 build/start 2단계 구조와 최적화
- Keycloak 26.x의 신기능과 프로덕션 체크리스트
Keycloak의 역사 — WildFly에서 Quarkus로
Keycloak은 2014년 Red Hat에서 시작된 프로젝트로, 초기에는 JBoss/WildFly 애플리케이션 서버 위에서 동작하는 전통적인 Java EE 애플리케이션이었습니다. WildFly 기반 배포판은 다음과 같은 한계가 있었습니다.
- standalone.xml 중심의 복잡한 XML 설정
- 느린 기동 시간(수십 초)과 큰 메모리 풋프린트
- 컨테이너/쿠버네티스 환경과의 부조화 (불변 인프라 패러다임과 충돌)
- Java EE 서브시스템 전체를 탑재한 무거운 런타임
2021년 Keycloak.X 프로젝트로 시작된 Quarkus 전환은 Keycloak 17(2022)에서 기본 배포판이 되었고, WildFly 배포판은 19를 끝으로 완전히 제거되었습니다. Quarkus 기반 전환의 핵심은 빌드 타임 최적화입니다.
| 항목 | WildFly 배포판 | Quarkus 배포판 |
|---|---|---|
| 설정 방식 | standalone.xml 편집 | conf 파일, 환경변수, CLI 옵션 |
| 기동 시간 | 20초 이상 | 수 초 이내 |
| 메모리 사용 | 높음 | 상대적으로 낮음 |
| 확장 배포 | EAR/WAR 모듈 | providers 디렉토리에 JAR |
| 컨테이너 친화성 | 낮음 | 매우 높음 (공식 이미지 최적화) |
| 설정 변경 반영 | 재시작 | build 단계 재실행 후 시작 |
Quarkus는 CDI 와이어링, 클래스패스 스캔, 설정 파싱 같은 작업을 가능한 한 빌드 타임에 끝내버립니다. 그래서 Keycloak도 "빌드 옵션"과 "런타임 옵션"이 명확히 분리되어 있으며, 이것이 뒤에서 다룰 kc.sh build 단계의 존재 이유입니다.
도메인 모델 — Realm, Client, User, Role, Group
Keycloak의 모든 개념은 realm을 정점으로 하는 트리 구조로 이해할 수 있습니다.
+--------------------------------------------------------------+
| Keycloak Server |
| |
| +--------------------+ +--------------------+ |
| | Realm: master | | Realm: production | |
| | (관리 전용 권장) | | | |
| +--------------------+ | +--------------+ | |
| | | Clients | | |
| | | - web-app | | |
| | | - api-gw | | |
| | | - cli-tool | | |
| | +--------------+ | |
| | +--------------+ | |
| | | Users | | |
| | | - alice |--+-- Groups |
| | | - bob | | Roles |
| | +--------------+ | |
| | +--------------+ | |
| | | Identity | | |
| | | Providers | | |
| | +--------------+ | |
| +--------------------+ |
+--------------------------------------------------------------+
Realm
realm은 사용자, 클라이언트, 역할, 그룹, 인증 플로우, 토큰 정책을 모두 포함하는 격리 단위입니다. 테넌트 경계라고 생각하면 됩니다. 운영 관점에서 중요한 원칙은 다음과 같습니다.
- master realm은 Keycloak 자체 관리용으로만 사용하고, 애플리케이션 사용자는 별도 realm에 둡니다.
- realm 수가 수백 개를 넘어가면 캐시와 기동 시간에 부담이 되므로, 멀티테넌시가 필요하다면 organizations 기능(26에서 정식화)을 먼저 검토합니다.
- realm별로 토큰 수명, 비밀번호 정책, 브루트포스 보호 설정이 독립적입니다.
Client
client는 Keycloak에게 인증을 위임하는 애플리케이션 또는 서비스입니다. OIDC 용어의 Relying Party, SAML 용어의 Service Provider에 해당합니다. 웹 앱, SPA, 모바일 앱, 백엔드 API, CLI 도구 모두 client로 등록됩니다.
User, Role, Group
- user는 realm에 소속된 인증 주체입니다. credential(비밀번호, OTP, passkey), attribute, federated identity 링크를 가집니다.
- role은 권한의 단위로, realm role과 client role 두 종류가 있습니다. client role은 특정 client 네임스페이스에 속합니다.
- group은 사용자 묶음이며 계층 구조를 가질 수 있고, group에 role을 매핑하면 소속 사용자가 role을 상속합니다.
- composite role은 다른 role들을 포함하는 role로, 남용하면 권한 추적이 어려워지므로 group 기반 매핑을 우선하는 것이 정석입니다.
role과 group의 사용 기준을 정리하면 다음과 같습니다.
| 기준 | Role | Group |
|---|---|---|
| 의미 | 권한(permission)의 표현 | 조직/사용자 집합의 표현 |
| 계층 구조 | composite로 흉내 가능 | 네이티브 지원 |
| 토큰 반영 | realm_access, resource_access 클레임 | groups 클레임 (protocol mapper 필요) |
| 권장 패턴 | 애플리케이션이 검사하는 단위 | role을 일괄 부여하는 운영 단위 |
Confidential vs Public Client
OAuth 2.0의 클라이언트 유형 구분은 Keycloak 설정에서 Client authentication 토글로 표현됩니다.
| 항목 | Confidential Client | Public Client |
|---|---|---|
| 시크릿 보관 | 가능 (서버 사이드) | 불가능 (브라우저/모바일) |
| 예시 | Spring Boot 백엔드, API 게이트웨이 | SPA, 네이티브 모바일 앱 |
| 클라이언트 인증 | client secret, private_key_jwt, mTLS | 없음 |
| PKCE | 권장 | 필수 (OAuth 2.1 기준) |
| token endpoint 접근 | 시크릿과 함께 | code_verifier와 함께 |
OAuth 2.1 드래프트는 모든 클라이언트에 PKCE를 의무화하고 Implicit/ROPC 그랜트를 제거했습니다. 2026년 시점에서 신규 클라이언트는 유형과 무관하게 PKCE(S256)를 켜는 것이 표준 관행입니다. Keycloak에서는 client의 Advanced 설정에서 Proof Key for Code Exchange Code Challenge Method를 S256으로 강제할 수 있습니다.
confidential client의 인증 방식도 진화하고 있습니다. 단순 client secret보다는 private_key_jwt(서명 기반)나 mTLS가 권장되며, 26.6에서 추가된 Federated client authentication을 사용하면 SPIFFE/SVID 같은 워크로드 아이덴티티 시스템이 발급한 JWT로 클라이언트를 인증할 수 있어 시크릿 자체를 제거할 수 있습니다.
Client Scope — 토큰 내용물의 조립 단위
client scope는 "토큰에 어떤 클레임과 role이 들어갈지"를 재사용 가능한 단위로 묶은 것입니다. protocol mapper(클레임 생성기)와 role scope mapping(role 필터)의 묶음이라고 보면 정확합니다.
- Default scope: 항상 토큰에 반영됩니다. profile, email, roles, web-origins가 기본입니다.
- Optional scope: 클라이언트가 scope 파라미터로 명시적으로 요청할 때만 반영됩니다. address, phone, offline_access 등이 해당합니다.
예를 들어 SPA가 다음과 같이 인가 요청을 보내면:
GET /realms/production/protocol/openid-connect/auth?
client_id=web-app
&response_type=code
&scope=openid profile email offline_access
&redirect_uri=https://app.example.com/callback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
openid, profile, email은 default scope로 자동 포함되고, offline_access는 optional scope이므로 명시적으로 요청해야 refresh token이 오프라인 토큰으로 발급됩니다. 토큰 비대화를 막으려면 default scope를 최소화하고, audience mapper로 aud 클레임을 명시적으로 관리하는 것이 좋습니다. 발급된 access token의 페이로드는 대략 다음과 같습니다.
{
"exp": 1765540800,
"iat": 1765540500,
"iss": "https://sso.example.com/realms/production",
"aud": ["api-gateway"],
"sub": "f3a2c1d0-1234-5678-9abc-def012345678",
"typ": "Bearer",
"azp": "web-app",
"scope": "openid profile email",
"realm_access": { "roles": ["user"] },
"resource_access": {
"api-gateway": { "roles": ["orders:read"] }
},
"preferred_username": "alice"
}
인증 플로우 엔진 — Browser Flow의 내부 구조
Keycloak의 인증은 authentication flow라는 트리 구조의 실행 엔진으로 동작합니다. browser flow가 가장 대표적이며, 사용자가 인가 엔드포인트에 도착했을 때 실행됩니다.
Browser Flow (기본 구조)
|
+-- Cookie [ALTERNATIVE]
| (SSO 세션 쿠키가 있으면 즉시 통과)
|
+-- Identity Provider Redirector [ALTERNATIVE]
| (kc_idp_hint가 있으면 외부 IdP로)
|
+-- Forms [ALTERNATIVE]
|
+-- Username Password Form [REQUIRED]
|
+-- Browser - Conditional 2FA [CONDITIONAL]
|
+-- Condition - User Configured [REQUIRED]
+-- OTP Form [ALTERNATIVE]
+-- WebAuthn Authenticator [ALTERNATIVE]
각 노드는 execution이라 부르며, 세 가지 요소로 구성됩니다.
- Authenticator — 실제 인증 로직(쿠키 검사, 비밀번호 폼, OTP 검증)을 수행하는 SPI 구현체
- Requirement — REQUIRED, ALTERNATIVE, CONDITIONAL, DISABLED 중 하나
- Sub-flow — execution들을 묶는 그룹 (자체 requirement를 가짐)
평가 규칙은 단순합니다. 같은 레벨에서 ALTERNATIVE 중 하나라도 성공하면 그 레벨은 통과, REQUIRED는 전부 성공해야 통과입니다. CONDITIONAL sub-flow는 내부의 condition execution이 참일 때만 REQUIRED처럼 동작합니다. 위 구조에서 "SSO 쿠키가 있으면 폼 생략, 없으면 비밀번호 입력 후 OTP가 설정된 사용자만 2FA"라는 동작이 이 규칙만으로 만들어집니다.
26.x에서는 로그인 폼에 passkeys가 통합되어(conditional/modal UI), WebAuthn conditional UI를 통해 사용자가 아이디 입력 없이 브라우저 자동완성에서 passkey를 선택해 로그인하는 흐름이 기본 테마에서 지원됩니다. 자세한 내용은 WebAuthn Level 3 스펙과 FIDO Alliance passkeys 문서를 참고하시기 바랍니다.
인증이 끝나면 user session이 생성되고, 클라이언트별 client session이 그 아래에 매달립니다. 이 세션들이 Infinispan 캐시(26부터는 기본적으로 DB 영속화)에 저장되며, SSO 쿠키(KEYCLOAK_IDENTITY)가 브라우저에 발급됩니다.
데이터베이스 스키마 개요
Keycloak은 JPA(Hibernate) 기반으로 관계형 데이터베이스에 모든 설정과 사용자를 저장합니다. 핵심 테이블 그룹을 개괄하면 다음과 같습니다.
REALM -- realm 정의와 정책 (토큰 수명 등 컬럼 다수)
REALM_ATTRIBUTE -- realm의 확장 속성 (key-value)
CLIENT -- 클라이언트 정의
CLIENT_ATTRIBUTES -- 클라이언트 확장 속성 (PKCE 강제 등)
CLIENT_SCOPE -- client scope 정의
PROTOCOL_MAPPER -- 클레임 매퍼 설정
USER_ENTITY -- 사용자 본체 (username, email, ...)
USER_ATTRIBUTE -- 사용자 커스텀 속성
CREDENTIAL -- 해시된 비밀번호, OTP, passkey 메타데이터
USER_ROLE_MAPPING -- 사용자-role 매핑
KEYCLOAK_GROUP -- 그룹 (계층은 PARENT_GROUP 컬럼)
GROUP_ROLE_MAPPING -- 그룹-role 매핑
KEYCLOAK_ROLE -- realm/client role 정의
FEDERATED_IDENTITY -- 외부 IdP 계정 링크
EVENT_ENTITY -- 로그인 이벤트 (활성화 시)
ADMIN_EVENT_ENTITY -- 관리 이벤트 감사 로그
OFFLINE_USER_SESSION -- 오프라인/영속 세션
OFFLINE_CLIENT_SESSION -- 클라이언트별 영속 세션
MIGRATION_MODEL -- 스키마 버전 추적 (Liquibase)
운영 시 알아두면 좋은 포인트입니다.
- 스키마 마이그레이션은 Liquibase가 기동 시 자동 수행합니다. 대규모 업그레이드 전에는 반드시 DB 백업이 필요합니다.
- EVENT_ENTITY는 보존 기간을 설정하지 않으면 무한히 자랍니다. 이벤트 만료 설정 또는 외부 로그 시스템 연동이 필수입니다.
- 속성 검색이 잦다면 USER_ATTRIBUTE에 인덱스 추가를 검토합니다(26은 주요 조회 경로에 인덱스를 보강했습니다).
- 직접 SQL로 데이터를 수정하는 것은 캐시 불일치를 일으키므로 금기입니다. 반드시 Admin API를 사용합니다.
kc.sh — Build와 Start의 2단계 구조
Quarkus 기반 Keycloak의 가장 큰 특징은 설정이 빌드 옵션과 런타임 옵션으로 나뉜다는 점입니다.
- 빌드 옵션: db 종류, 캐시 스택, health/metrics 활성화, features 플래그 등 — 변경 시 재빌드 필요
- 런타임 옵션: DB URL/계정, hostname, 로그 레벨, HTTPS 인증서 경로 등 — 시작 시 지정
# 1단계: 빌드 (불변 이미지를 만들 때 한 번만)
bin/kc.sh build \
--db=postgres \
--health-enabled=true \
--metrics-enabled=true \
--features=token-exchange
# 2단계: 시작 (런타임 옵션만 지정, --optimized로 재빌드 생략)
bin/kc.sh start --optimized \
--db-url=jdbc:postgresql://db.internal:5432/keycloak \
--db-username=keycloak \
--db-password=changeme \
--hostname=sso.example.com \
--proxy-headers=xforwarded \
--http-enabled=true
컨테이너 이미지를 만들 때는 빌드 단계를 Dockerfile에서 미리 수행해 기동 시간을 줄이는 것이 정석입니다.
FROM quay.io/keycloak/keycloak:26.6 AS builder
ENV KC_DB=postgres
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:26.6
COPY /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]
start-dev 모드는 개발 전용입니다. 캐시가 로컬이고 HTTP가 열려 있으며 hostname 검증이 느슨하므로 프로덕션에서 절대 사용하면 안 됩니다.
설정 방법 — 환경변수, conf 파일, CLI
같은 옵션을 세 가지 방법으로 지정할 수 있으며, 우선순위는 CLI 인자가 가장 높고 그다음 환경변수, conf 파일 순입니다.
# 방법 1: CLI 인자
bin/kc.sh start --db-url-host=db.internal --log-level=info
# 방법 2: 환경변수 (옵션명을 대문자+언더스코어로, KC_ 프리픽스)
export KC_DB_URL_HOST=db.internal
export KC_LOG_LEVEL=info
# 방법 3: conf/keycloak.conf 파일
# db-url-host=db.internal
# log-level=info
쿠버네티스 환경에서는 환경변수 방식이 ConfigMap/Secret과 자연스럽게 결합하므로 가장 널리 쓰입니다. 시크릿 값은 파일로 마운트한 뒤 옵션 값에 file 참조를 쓰는 방식도 지원됩니다. 전체 옵션 목록은 kc.sh의 서브커맨드별 help와 공식 서버 설정 가이드에서 확인할 수 있습니다.
Keycloak 26.x 신기능 정리
26.x 시리즈, 특히 26.6.0 릴리스는 굵직한 기능이 많습니다.
Workflows (realm 관리 자동화)
장기 미사용 계정 비활성화, 일정 기간 후 권한 회수 같은 수명주기 작업을 서버 내에서 선언적으로 자동화하는 기능입니다. 기존에는 외부 배치 잡으로 Admin API를 호출해 처리하던 일을 realm 설정으로 내재화할 수 있습니다.
Zero-downtime rolling patch
26.6부터 패치 버전(26.6.0에서 26.6.1 등) 간에는 캐시 호환성이 보장되어, 쿠버네티스 rolling update 시 클러스터 전체 재기동 없이 파드를 순차 교체할 수 있습니다. Operator 사용 시 update strategy를 Auto로 두면 호환성 검사 후 자동으로 롤링이 수행됩니다.
Federated client authentication
클라이언트가 client secret 대신 외부 신뢰 기관(예: SPIFFE 워크로드 아이덴티티)이 발급한 JWT로 인증합니다. 시크릿 배포/로테이션 문제를 근본적으로 제거하는 방향입니다.
JWT Authorization Grant
RFC 7523 기반으로, 외부에서 발급된 JWT assertion을 access token으로 교환하는 그랜트가 정식 지원됩니다. 시스템 간 연동(machine-to-machine)에서 토큰 브로커링이 단순해집니다.
그 외
- 로그인 폼 passkeys 통합 (conditional/modal UI)
- FAPI 2.0 Security Profile & Message Signing Final 지원
- EdDSA 서명 알고리즘 지원
- OAuth Client ID Metadata Document(CIMD) 실험 지원 — Keycloak을 MCP(Model Context Protocol) authorization server로 쓰는 시나리오가 열렸습니다. AI 에이전트가 사전 등록 없이 메타데이터 URL로 자신을 식별하는 패턴입니다.
프로덕션 체크리스트
마지막으로 프로덕션 투입 전 점검 목록입니다.
[ ] master realm에 애플리케이션 사용자를 두지 않았는가
[ ] admin 콘솔 접근을 별도 hostname 또는 네트워크로 제한했는가
(--hostname-admin 옵션, 방화벽/Ingress 분리)
[ ] HTTPS 종단과 --proxy-headers 설정이 일치하는가
[ ] DB는 프로덕션급(PostgreSQL 권장)이며 커넥션 풀 크기를 산정했는가
[ ] 토큰 수명: access token 5-15분, SSO idle/max 정책 수립했는가
[ ] refresh token rotation 또는 revoke-refresh-token을 켰는가
[ ] 모든 신규 클라이언트에 PKCE S256 강제했는가
[ ] 와일드카드 redirect URI를 금지했는가 (정확한 URI만 등록)
[ ] brute force detection을 켰는가
[ ] 이벤트 로깅 + 보존 기간 + 외부 SIEM 연동을 구성했는가
[ ] health/metrics 엔드포인트를 켜고 모니터링에 연결했는가
[ ] 백업: DB 백업 + realm export를 주기적으로 수행하는가
[ ] 업그레이드 경로: 릴리스 노트의 breaking change를 추적하는가
[ ] features 플래그로 쓰지 않는 기능(예: 임시 admin 계정)을 정리했는가
특히 토큰 정책은 OAuth 2.0 Security BCP(RFC 9700)를 기준으로 잡는 것을 권장합니다. sender-constrained token(DPoP/mTLS), 짧은 access token 수명, refresh token rotation이 핵심입니다.
트러블슈팅 — 자주 만나는 문제와 원인
1. Invalid redirect_uri 오류
가장 흔한 초기 설정 오류입니다. 클라이언트에 등록된 redirect URI와 인가 요청의 redirect_uri가 정확히 일치하지 않을 때 발생합니다.
원인 체크리스트:
- 프로토콜 불일치 (http vs https)
- 트레일링 슬래시 유무 차이
- 포트 누락 (https://app.example.com vs https://app.example.com:443)
- 와일드카드(*)에 의존하다가 프로덕션에서 제거된 경우
해결은 단순합니다. 클라이언트 설정의 Valid redirect URIs에 콜백 URL을 정확히(가능하면 와일드카드 없이) 등록합니다.
2. 리버스 프록시 뒤에서 무한 리다이렉트 또는 잘못된 issuer
프록시가 TLS를 종단하는데 Keycloak이 이를 모르면, 발급되는 토큰의 iss 클레임과 리다이렉트 URL이 내부 주소로 생성됩니다.
# 프록시가 X-Forwarded-* 헤더를 설정한다면
bin/kc.sh start --optimized \
--hostname=sso.example.com \
--proxy-headers=xforwarded \
--http-enabled=true
# 검증: OIDC discovery 문서의 issuer가 외부 주소인지 확인
curl -s https://sso.example.com/realms/production/.well-known/openid-configuration
discovery 문서의 issuer, authorization_endpoint가 모두 외부 hostname으로 나오는지 확인하면 됩니다.
3. 빌드 옵션을 런타임에 바꿨는데 반영되지 않음
db 종류나 features 같은 빌드 옵션은 start 시점에 지정해도 무시되거나 자동 재빌드를 유발합니다. --optimized 플래그를 쓰고 있다면 무시되고, 아니라면 기동이 느려집니다. 빌드 옵션 변경은 반드시 이미지 재빌드로 처리해야 합니다.
4. 업그레이드 후 로그인 세션 전부 로그아웃
26 이전 버전에서는 인메모리 세션이 기본이어서 전체 재기동 시 세션이 증발했습니다. 26부터 persistent user sessions가 기본이므로 세션이 DB에 보존되지만, 마이너 버전 간 캐시 프로토콜 비호환으로 인한 풀 재기동은 여전히 필요할 수 있습니다. 릴리스 노트의 업그레이드 섹션을 반드시 확인하시기 바랍니다.
5. 메모리 사용량이 계속 증가
흔한 원인은 다음 세 가지입니다.
1. EVENT_ENTITY / ADMIN_EVENT_ENTITY 무한 증가
-> 이벤트 만료(expiration) 설정, 외부 SIEM으로 오프로드
2. 오프라인 세션 과다 (offline_access를 모든 클라이언트에 부여)
-> offline_access scope를 꼭 필요한 클라이언트로 제한
3. JVM 힙 미설정으로 컨테이너 한계까지 캐시 성장
-> JAVA_OPTS_KC_HEAP 또는 컨테이너 메모리 기반 자동 산정 확인
안티패턴 모음
- master realm을 일반 사용자 로그인에 사용 — 권한 상승 위험과 관리 복잡도를 동시에 키웁니다.
- ROPC(Resource Owner Password Credentials) 그랜트 사용 — OAuth 2.1에서 제거된 패턴입니다. CLI라면 Device Authorization Grant를 사용합니다.
- access token 수명을 수 시간으로 설정 — 탈취 시 피해 범위가 그대로 커집니다. 5-15분 + refresh rotation이 표준입니다.
- DB를 직접 UPDATE — 캐시와 DB의 불일치로 예측 불가능한 동작을 유발합니다. Admin REST API 또는 Admin CLI(kcadm.sh)만 사용합니다.
- 모든 role을 composite role로 엮기 — 권한 감사가 불가능해집니다. group 기반 매핑으로 풀어내는 것이 낫습니다.
마치며
Keycloak 26은 "WildFly 시절의 무거운 SSO 서버"라는 인상과는 완전히 다른 소프트웨어가 되었습니다. Quarkus 기반의 빌드/런타임 분리, 선언적 설정, 쿠버네티스 친화적 운영 모델, 그리고 passkeys와 AI 에이전트 아이덴티티까지 포괄하는 표준 지원은 2026년의 identity-first Zero Trust 아키텍처에서 Keycloak을 중심 컴포넌트로 만들기에 충분합니다.
이 글에서 다룬 도메인 모델과 런타임 구조를 이해했다면, 다음 단계는 고가용성 클러스터링과 SPI 확장 개발입니다. 각각 별도 글에서 깊이 다루겠습니다.