- Published on
차세대 디지털 아이덴티티와 SSO, 그리고 Keycloak 실무 완벽 가이드
- Authors
- Name
- 1. 패스워드 피로와 SSO의 등장
- 2. SSO 구현 아키텍처
- 3. 기술 표준 비교: SAML vs OAuth 2.0 vs OIDC
- 4. Keycloak 핵심 아키텍처
- 5. 프로덕션 배포
- 6. 인증(Authentication) 플로우
- 7. 인가(Authorization) 체계
- 8. Identity Brokering과 Social Login
- 9. 어플리케이션 연동 실무
- 10. MSA 환경의 Token Relay 패턴
- 11. Admin REST API 활용
- 12. 고가용성(HA) 아키텍처
- 13. 모니터링 및 관측성
- 14. 마무리
- References
1. 패스워드 피로와 SSO의 등장
현대 비즈니스 환경에서 사용하는 어플리케이션이 기하급수적으로 늘어나면서 **패스워드 피로도(Password Fatigue)**는 심각한 보안 위협이자 생산성 저하의 원인이 되었다. 이를 해결하기 위해 등장한 SSO(Single Sign-On) 기술은 단 한 번의 인증으로 신뢰 관계가 구축된 모든 서비스에 접근할 수 있게 해준다.
SSO를 통해 얻을 수 있는 핵심 가치는 다음과 같다.
- MFA 중앙화: 다요소 인증을 한 곳에서 관리하여 일관된 보안 정책 적용
- 즉각적 권한 회수: 퇴사자나 역할 변경 시 IdP에서 한 번에 접근 차단
- 제로 트러스트 초석: "Never Trust, Always Verify" 원칙의 기반
2. SSO 구현 아키텍처
SSO 시스템은 환경에 따라 세 가지 모델로 나뉜다.
| 모델 | 방식 | 적합 환경 |
|---|---|---|
| 에이전트 기반 | 개별 서버에 에이전트 설치 | 레거시 환경 |
| 에이전트리스 | API Gateway / Reverse Proxy로 트래픽 제어 | 클라우드 네이티브 |
| 아이덴티티 페더레이션 | 도메인 간 아이덴티티 공유 | B2B 협업, 멀티 조직 |
3. 기술 표준 비교: SAML vs OAuth 2.0 vs OIDC
| 항목 | SAML 2.0 | OAuth 2.0 | OIDC |
|---|---|---|---|
| 목적 | 인증(Authentication) | 인가(Authorization) | 인증 + 인가 |
| 데이터 형식 | XML (Assertion) | JSON | JSON (JWT) |
| 토큰 | SAML Assertion | Access Token | ID Token + Access Token |
| 주요 사용처 | 엔터프라이즈 웹앱 | API 권한 위임 | 모바일, SPA, MSA |
| 복잡도 | 높음 | 중간 | 낮음 |
**OIDC(OpenID Connect)**는 OAuth 2.0 위에 아이덴티티 계층을 얹어 JWT 형태로 가볍게 인증을 처리하며, 현대 마이크로서비스 환경의 사실상 표준이다.
4. Keycloak 핵심 아키텍처
Keycloak은 SAML, OAuth 2.0, OIDC 표준을 모두 지원하는 오픈소스 IAM 솔루션이다. Red Hat이 지원하며 CNCF 인큐베이팅 프로젝트다.
4.1 핵심 계층 구조
Keycloak Instance
└── Realm (논리적 격리 단위, 테넌트)
├── Clients (인증을 요청하는 어플리케이션)
├── Users (사용자)
├── Groups (사용자 그룹)
├── Roles (Realm Roles + Client Roles)
├── Identity Providers (외부 IdP 연동)
├── Authentication Flows (인증 흐름 정의)
└── Authorization Services (세밀한 인가 정책)
- Realm: 독립된 테넌트 공간.
masterRealm은 관리용, 실제 서비스는 별도 Realm 생성 - Client: OAuth/OIDC 클라이언트 어플리케이션 (Confidential / Public / Bearer-only)
- Roles & Groups: 권한 관리의 핵심. Realm Role은 전체 범위, Client Role은 특정 클라이언트 범위
4.2 기술 스택
| 컴포넌트 | 역할 |
|---|---|
| Quarkus | 고성능 Java 프레임워크 기반 런타임 |
| Infinispan | 인메모리 분산 캐시 (세션, 토큰 동기화) |
| PostgreSQL (등) | 사용자, 정책 등 영구 데이터 저장 |
5. 프로덕션 배포
5.1 Docker 배포
개발 모드 (빠른 시작, H2 내장 DB):
docker run --name keycloak -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \
quay.io/keycloak/keycloak:latest \
start-dev
프로덕션 모드 (TLS + PostgreSQL 필수):
docker run --name keycloak -p 8443:8443 -p 9000:9000 -m 2g \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \
quay.io/keycloak/keycloak:latest start \
--hostname=https://auth.example.com \
--https-certificate-file=/opt/keycloak/conf/cert.pem \
--https-certificate-key-file=/opt/keycloak/conf/key.pem \
--db=postgres \
--db-url=jdbc:postgresql://db-host:5432/keycloak \
--db-username=keycloak \
--db-password=change_me \
--health-enabled=true \
--metrics-enabled=true
5.2 Kubernetes Operator 배포
# CRD 및 Operator 설치
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
kubectl create namespace keycloak
kubectl -n keycloak apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/kubernetes.yml
Keycloak CR 매니페스트:
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: production-kc
namespace: keycloak
spec:
instances: 3
db:
vendor: postgres
host: postgres-db
usernameSecret:
name: keycloak-db-secret
key: username
passwordSecret:
name: keycloak-db-secret
key: password
http:
tlsSecret: keycloak-tls-secret
hostname:
hostname: auth.example.com
proxy:
headers: xforwarded
5.3 keycloak.conf 프로덕션 설정
# Hostname
hostname=https://auth.example.com
hostname-admin=https://admin.auth.example.com
# Database
db=postgres
db-url=jdbc:postgresql://db:5432/keycloak
db-username=keycloak
db-password=${vault.db-password}
db-pool-max-size=100
# TLS
http-enabled=false
https-port=8443
https-certificate-file=/etc/certs/tls.crt
https-certificate-key-file=/etc/certs/tls.key
# Proxy
proxy-headers=xforwarded
# Cache / Clustering
cache=ispn
# Health & Metrics (port 9000)
health-enabled=true
metrics-enabled=true
# Logging
log=console,file
log-console-output=json
6. 인증(Authentication) 플로우
6.1 OIDC 엔드포인트
모든 엔드포인트는 Well-Known URL에서 자동 탐색 가능하다.
GET /realms/{realm}/.well-known/openid-configuration
| 엔드포인트 | 경로 |
|---|---|
| Authorization | /realms/{realm}/protocol/openid-connect/auth |
| Token | /realms/{realm}/protocol/openid-connect/token |
| Userinfo | /realms/{realm}/protocol/openid-connect/userinfo |
| Logout | /realms/{realm}/protocol/openid-connect/logout |
| JWKS | /realms/{realm}/protocol/openid-connect/certs |
| Introspect | /realms/{realm}/protocol/openid-connect/token/introspect |
6.2 Authorization Code Flow + PKCE
웹 애플리케이션과 SPA/모바일에 권장되는 표준 플로우다.
1단계 - 인가 요청 (브라우저 리다이렉트):
GET /realms/myrealm/protocol/openid-connect/auth?
response_type=code&
client_id=my-app&
redirect_uri=https://myapp.example.com/callback&
scope=openid profile email&
state=random-state-value&
code_challenge=BASE64URL(SHA256(code_verifier))&
code_challenge_method=S256
2단계 - 토큰 교환 (서버 사이드):
curl -X POST \
https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://myapp.example.com/callback" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "code_verifier=ORIGINAL_CODE_VERIFIER"
PKCE는 코드 가로채기 공격을 방지한다. code_verifier는 클라이언트가 생성한 랜덤 문자열이고, code_challenge는 그 SHA256 해시다. 토큰 교환 시 원본 code_verifier를 제출하여 검증한다.
6.3 Client Credentials Flow
서비스 간(M2M) 인증에 사용한다.
curl -X POST \
https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=my-service" \
-d "client_secret=my-service-secret" \
-d "scope=openid"
Confidential 클라이언트에는 내장 Service Account가 있다. Clients > my-service > Service Account Roles에서 역할을 할당한다.
6.4 Client 타입 선택 가이드
| 타입 | 시크릿 | 사용처 |
|---|---|---|
| Confidential | 있음 | 서버 사이드 웹앱, 백엔드 서비스 |
| Public | 없음 | SPA, 모바일 앱 (PKCE 필수) |
| Bearer-only | 검증만 | REST API 서비스 (Resource Server) |
7. 인가(Authorization) 체계
7.1 RBAC (Role-Based Access Control)
역할 기반 접근 제어는 사용자에게 역할을 할당하고, 역할에 따라 접근을 허용/거부한다.
- Realm Role: 전체 Realm 범위 (예:
admin,user) - Client Role: 특정 클라이언트 범위 (예:
my-app:editor) - Composite Role: 여러 역할을 묶은 상위 역할
7.2 ABAC (Attribute-Based Access Control)
속성 기반 접근 제어는 사용자의 IP, 접속 시간, 부서 정보 등을 종합하여 동적으로 판단한다.
Keycloak Authorization Services는 다양한 정책 타입을 지원한다.
| 정책 타입 | 설명 |
|---|---|
| Role-based | Realm/Client 역할 평가 |
| Group-based | 그룹 멤버십 평가 |
| Time-based | 시간대/기간 제한 |
| Client-based | 요청 클라이언트 제한 |
| JavaScript | 커스텀 JS 조건 |
| Regex | 패턴 매칭 |
| Aggregated | 여러 정책 조합 |
7.3 권한 모델
"X(사용자)가 Y(액션)를 Z(리소스)에 수행할 수 있는가?"
# RPT(Requesting Party Token) 요청으로 권한 확인
curl -X POST \
https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
-d "audience=my-resource-server"
결정 전략:
- Affirmative: 하나의 정책이라도 허용하면 접근 허용
- Unanimous: 모든 정책이 허용해야 접근 허용
8. Identity Brokering과 Social Login
Keycloak은 외부 IdP와의 연동을 기본 지원한다.
8.1 지원 IdP
| 유형 | 지원 목록 |
|---|---|
| Social Login | Google, GitHub, Facebook, Microsoft, LinkedIn, GitLab 등 |
| 프로토콜 기반 | OIDC v1.0, OAuth 2.0, SAML 2.0 |
8.2 Google IdP 설정 예시
- Google Cloud Console에서 OAuth 2.0 자격증명 생성
- Redirect URI:
https://auth.example.com/realms/{realm}/broker/google/endpoint - Keycloak Admin Console > Identity Providers > Google 추가
- Client ID와 Client Secret 입력
8.3 LDAP/Active Directory 연동
User Federation을 통해 LDAP과 연동할 수 있다.
| 설정 | Active Directory | 일반 LDAP |
|---|---|---|
| Connection URL | ldaps://ad-server:636 | ldap://ldap-server:389 |
| Users DN | OU=Users,DC=corp,DC=example,DC=com | ou=People,dc=example,dc=com |
| Username Attribute | sAMAccountName | uid |
| UUID Attribute | objectGUID | entryUUID |
동기화 모드:
- READ_ONLY: LDAP에서 읽기만 (가장 안전)
- WRITABLE: Keycloak에서 LDAP 속성 수정 가능
- UNSYNCED: 변경사항을 Keycloak 로컬에만 저장
9. 어플리케이션 연동 실무
9.1 Spring Boot Resource Server
application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/realms/myrealm
jwk-set-uri: https://auth.example.com/realms/myrealm/protocol/openid-connect/certs
SecurityConfig.java:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName("realm_access.roles");
converter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
}
9.2 Next.js (Auth.js / NextAuth.js)
.env.local:
AUTH_KEYCLOAK_ID=my-nextjs-app
AUTH_KEYCLOAK_SECRET=my-client-secret
AUTH_KEYCLOAK_ISSUER=https://auth.example.com/realms/myrealm
auth.ts (Auth.js v5):
import NextAuth from 'next-auth'
import Keycloak from 'next-auth/providers/keycloak'
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Keycloak],
})
Keycloak Client 설정에서 Redirect URI를 등록한다:
https://myapp.example.com/api/auth/callback/keycloak
9.3 React SPA (keycloak-js)
import Keycloak from 'keycloak-js'
const keycloak = new Keycloak({
url: 'https://auth.example.com',
realm: 'myrealm',
clientId: 'my-spa',
})
// Authorization Code Flow + PKCE로 초기화
const authenticated = await keycloak.init({
onLoad: 'login-required',
pkceMethod: 'S256',
checkLoginIframe: false,
})
// API 호출 시 토큰 사용
const response = await fetch('/api/data', {
headers: {
Authorization: `Bearer ${keycloak.token}`,
},
})
// 토큰 만료 전 자동 갱신
keycloak.onTokenExpired = () => {
keycloak.updateToken(30).catch(() => keycloak.login())
}
// 역할 확인
keycloak.hasRealmRole('admin')
keycloak.hasResourceRole('editor', 'my-resource-server')
9.4 Node.js Express
const express = require('express')
const session = require('express-session')
const Keycloak = require('keycloak-connect')
const app = express()
const memoryStore = new session.MemoryStore()
app.use(
session({
secret: 'my-secret',
resave: false,
saveUninitialized: true,
store: memoryStore,
})
)
const keycloak = new Keycloak(
{ store: memoryStore },
{
realm: 'myrealm',
'auth-server-url': 'https://auth.example.com/',
resource: 'my-node-app',
}
)
app.use(keycloak.middleware())
// 인증 필수 엔드포인트
app.get('/api/protected', keycloak.protect(), (req, res) => {
res.json({ message: 'Authenticated!' })
})
// 특정 역할 필수
app.get('/api/admin', keycloak.protect('realm:admin'), (req, res) => {
res.json({ message: 'Admin access granted' })
})
app.listen(3000)
10. MSA 환경의 Token Relay 패턴
마이크로서비스 아키텍처에서 API Gateway가 Keycloak과 연동하여 JWT 토큰을 획득한 후, 내부 서비스로 요청을 전달하는 패턴이다.
[Client] → [API Gateway] → [Keycloak] (토큰 획득/검증)
↓
Authorization: Bearer <JWT>
↓
[Service A] → [Service B] → [Service C]
각 서비스가 IdP와 직접 통신할 필요 없이 JWT의 서명만 로컬에서 검증하므로 뛰어난 성능과 보안성을 확보할 수 있다. Keycloak의 JWKS 엔드포인트(/protocol/openid-connect/certs)에서 공개키를 가져와 캐싱하면 된다.
11. Admin REST API 활용
11.1 토큰 획득
# 패스워드 방식
ACCESS_TOKEN=$(curl -s -X POST \
"https://auth.example.com/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin-password" | jq -r '.access_token')
# Service Account 방식 (자동화 권장)
ACCESS_TOKEN=$(curl -s -X POST \
"https://auth.example.com/realms/master/protocol/openid-connect/token" \
-d "grant_type=client_credentials" \
-d "client_id=my-admin-client" \
-d "client_secret=my-admin-secret" | jq -r '.access_token')
11.2 사용자 관리
# 사용자 생성
curl -X POST \
"https://auth.example.com/admin/realms/myrealm/users" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"email": "newuser@example.com",
"enabled": true,
"credentials": [{
"type": "password",
"value": "temp-password",
"temporary": true
}]
}'
# 사용자 검색
curl -X GET \
"https://auth.example.com/admin/realms/myrealm/users?username=johndoe" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# 역할 할당
curl -X POST \
"https://auth.example.com/admin/realms/myrealm/users/{user-id}/role-mappings/realm" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '[{"id": "ROLE_ID", "name": "admin"}]'
12. 고가용성(HA) 아키텍처
12.1 분산 아키텍처
┌─────────────┐
│ Load Balancer│
└──────┬──────┘
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Keycloak 1│ │Keycloak 2│ │Keycloak 3│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ Infinispan │ (세션 동기화) │
└──────────────┴───────────────┘
│
┌──────┴──────┐
│ PostgreSQL │
│ (공유 DB) │
└─────────────┘
- 정적 데이터 (사용자, 정책): 공유 DB (PostgreSQL)에 저장
- 동적 데이터 (세션, 토큰): Infinispan 분산 캐시로 노드 간 동기화
12.2 Kubernetes Health Probe 설정
spec:
containers:
- name: keycloak
livenessProbe:
httpGet:
path: /health/live
port: 9000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 9000
initialDelaySeconds: 30
periodSeconds: 10
startupProbe:
httpGet:
path: /health/started
port: 9000
failureThreshold: 30
periodSeconds: 5
12.3 Cross-DC 복제 (외부 Infinispan)
지리적으로 분리된 데이터센터 간 동기화가 필요한 경우:
cache=ispn
cache-remote-host=infinispan.example.com
cache-remote-port=11222
cache-remote-username=keycloak
cache-remote-password=change_me
cache-remote-tls-enabled=true
13. 모니터링 및 관측성
13.1 Prometheus 메트릭
# prometheus.yml
scrape_configs:
- job_name: 'keycloak'
metrics_path: '/metrics'
scheme: https
static_configs:
- targets: ['keycloak-host:9000']
이벤트 메트릭 활성화:
bin/kc.sh start \
--metrics-enabled=true \
--event-metrics-user-enabled=true \
--event-metrics-user-tags=realm,client,idp \
--event-metrics-user-events=LOGIN,LOGIN_ERROR,LOGOUT,TOKEN_REFRESH
13.2 Grafana 대시보드
Keycloak 공식 Grafana 대시보드를 제공한다.
| 대시보드 | 용도 |
|---|---|
keycloak-troubleshooting-dashboard.json | SLI 및 진단 분석 |
keycloak-capacity-planning-dashboard.json | 부하 추정, 로그인 플로우 분석 |
다운로드: github.com/keycloak/keycloak-grafana-dashboard
14. 마무리
성공적인 SSO 구축을 위한 핵심 요약이다.
- 프로토콜: OIDC를 기본으로 선정하고, 레거시 엔터프라이즈 앱에만 SAML 적용
- 인가 체계: RBAC으로 기본 구조를 잡고, 세밀한 제어가 필요한 경우 ABAC 결합
- 고가용성: 최소 3개 노드 + Infinispan 분산 캐시 + 공유 DB 구성
- 어플리케이션 연동: Spring Boot는 Resource Server JWT 검증, SPA는 keycloak-js + PKCE, Next.js는 Auth.js 활용
- 모니터링: Prometheus 메트릭 + Grafana 대시보드로 로그인 실패율, 토큰 발급량 등 추적
Keycloak은 이 모든 것을 오픈소스로 제공하는 차세대 디지털 아이덴티티 거버넌스의 핵심이다.
References
- Keycloak Official Documentation. https://www.keycloak.org/guides
- Keycloak Production Configuration. https://www.keycloak.org/server/configuration-production
- Keycloak Operator Deployment. https://www.keycloak.org/operator/basic-deployment
- Keycloak OIDC Integration. https://www.keycloak.org/securing-apps/oidc-layers
- Keycloak JavaScript Adapter. https://www.keycloak.org/securing-apps/javascript-adapter
- Keycloak Node.js Adapter. https://www.keycloak.org/securing-apps/nodejs-adapter
- Keycloak Authorization Services. https://www.keycloak.org/docs/latest/authorization_services/
- Keycloak Admin REST API. https://www.keycloak.org/docs-api/latest/rest-api/index.html
- Keycloak Distributed Caching. https://www.keycloak.org/server/caching
- Keycloak High Availability. https://www.keycloak.org/high-availability/introduction
- Keycloak Metrics & Health. https://www.keycloak.org/observability/configuration-metrics
- Keycloak Grafana Dashboards. https://github.com/keycloak/keycloak-grafana-dashboard
- Auth.js Keycloak Provider. https://authjs.dev/getting-started/providers/keycloak
- OAuth 2.0 RFC 6749. https://datatracker.ietf.org/doc/html/rfc6749
- OpenID Connect Core 1.0. https://openid.net/specs/openid-connect-core-1_0.html