Keycloak 서버 구축 (SAML 2.0)

Keycloak 설치

설치 준비

준비

[RockyLinux]

dnf update -y
dnf install -y curl wget tar gzip unzip vim bind-utils openssl jq policycoreutils-python-utils gnupg2
#dnf install -y java-21-openjdk java-21-openjdk-devel
  • jdk 21은 /opt/jdk에 수동으로 설치한다.

[Debian/Ubuntu]

apt update
apt install -y curl wget tar gzip unzip vim dnsutils openssl jq policycoreutils-python-utils gnupg
#apt install -y openjdk-21-jdk
  • jdk 21은 /opt/jdk에 수동으로 설치한다.
java -version

postgresql 설치

PostgreSQL 16 설치

[RockyLinux]

dnf module list postgresql
dnf module reset postgresql -y
dnf module enable postgresql:16 -y
dnf install -y postgresql-server postgresql-contrib

[Debian/Ubuntu]

1. 필수 패키지 설치 및 저장소 키 추가

apt update
apt install -y curl ca-certificates gnupg
install -d /etc/apt/keyrings
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg

2. PostgreSQL 공식 저장소 추가

echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list

3. PostgreSQL 16 및 모듈(contrib) 설치

apt update
apt install -y postgresql-16 postgresql-contrib-16

PostgreSQL 초기화

[RockyLinux]

postgres --version
postgresql-setup --initdb
systemctl enable postgresql
cp -f /var/lib/pgsql/data/pg_hba.conf /var/lib/pgsql/data/pg_hba.conf.orig

[Debian/Ubuntu]

sudo -u postgres psql --version

Postgresql의 IPv4 접속 방식 변경

[RockyLinux] vi /var/lib/pgsql/data/pg_hba.conf

host    all             all             127.0.0.1/32            ident
을
host    all             all             127.0.0.1/32            scram-sha-256
로 수정
systemctl restart postgresql

[Debian/Ubuntu]

vi /etc/postgresql/16/main/pg_hba.conf

[Debian/Ubuntu] 은 수정 필요 없음.

systemctl restart postgresql

Keycloak DB 및 계정 생성

sudo -u postgres psql

CREATE USER u_keycloak WITH PASSWORD 'Pass!234';
CREATE DATABASE db_keycloak OWNER u_keycloak ENCODING 'UTF8';
\l
\q

psql -h 127.0.0.1 -U u_keycloak -d db_keycloak

SELECT current_database();
\q

postfix 설치 (이메일 발송용)

postfix 설치

[RockyLinux] dnf install -y postfix mailx

[Debian/Ubuntu] apt install -y postfix mailutils (Internet Site 타입으로 설치)

postfix 설정

설정파일에서 아래 부분을 수정한다. vi /etc/postfix/main.cf

myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain

inet_interfaces = loopback-only
inet_protocols = ipv4

mydestination = $myhostname, localhost.$mydomain, localhost

relayhost =

mynetworks = 127.0.0.0/8

smtp_tls_security_level = may
smtp_tls_loglevel = 1
postfix check
systemctl enable postfix
systemctl restart postfix

keycloak 설치

최신 압축 파일 다운로드 (필요시 버전 변경)

cd /tmp
KEYCLOAK_VERSION="26.7.2"
wget "https://github.com/keycloak/keycloak/releases/download/${KEYCLOAK_VERSION}/keycloak-${KEYCLOAK_VERSION}.tar.gz"

/opt 디렉터리에 압축 해제 및 심볼릭 링크 생성

tar -xvzf keycloak-${KEYCLOAK_VERSION}.tar.gz -C /opt/
ln -s /opt/keycloak-${KEYCLOAK_VERSION} /opt/keycloak
rm -f keycloak-${KEYCLOAK_VERSION}.tar.gz

사용자 생성

useradd --system --home-dir /opt/keycloak --shell /sbin/nologin keycloak
chown -R keycloak:keycloak /opt/keycloak-${KEYCLOAK_VERSION}
chown -R keycloak:keycloak /opt/keycloak
chmod 600 /opt/keycloak/conf/keycloak.conf
ls -ld /opt/keycloak*

Keycloak 기본 설정

cp -f /opt/keycloak/conf/keycloak.conf /opt/keycloak/conf/keycloak.conf.orig
vi /opt/keycloak/conf/keycloak.conf
# =========================================================
# Database
# =========================================================
db=postgres
db-username=u_keycloak
db-password=Pass!234
db-url=jdbc:postgresql://127.0.0.1:5432/db_keycloak

# =========================================================
# HTTP / HTTPS
# =========================================================
http-enabled=true
http-port=8080
http-host=0.0.0.0

# =========================================================
# Hostname
# =========================================================
hostname=https://sso.example.com

# =========================================================
# Reverse Proxy
# 10.10.10.1 : apache-httpd reverse proxy server
# =========================================================
proxy-headers=xforwarded
proxy-trusted-addresses=10.10.10.1

# =========================================================
# Health / Metrics
# =========================================================
health-enabled=true
metrics-enabled=true

# Management interface
http-management-host=127.0.0.1
http-management-port=9000
http-management-scheme=http

# =========================================================
# Logging
# =========================================================
log=console,file
log-level=info

log-file=/var/log/keycloak/keycloak.log
log-file-rotation-enabled=true
log-file-rotation-max-file-size=50M
log-file-rotation-max-backup-index=10

keycloak 로그 디렉터리 생성

mkdir -p /var/log/keycloak
chown keycloak:keycloak /var/log/keycloak
chmod 750 /var/log/keycloak

keycloak Build

sudo -u keycloak JAVA_HOME=/opt/jdk /opt/keycloak/bin/kc.sh build
sudo -u keycloak JAVA_HOME=/opt/jdk /opt/keycloak/bin/kc.sh show-config

초기 관리자 생성

export KEYCLOAK_BOOTSTRAP_PASSWORD='Pass!234'
sudo -u keycloak env KEYCLOAK_BOOTSTRAP_PASSWORD="$KEYCLOAK_BOOTSTRAP_PASSWORD" JAVA_HOME=/opt/jdk /opt/keycloak/bin/kc.sh bootstrap-admin user --username admin --password:env KEYCLOAK_BOOTSTRAP_PASSWORD
unset KEYCLOAK_BOOTSTRAP_PASSWORD

keycloak 수동 실행 테스트

sudo -u keycloak JAVA_HOME=/opt/jdk /opt/keycloak/bin/kc.sh start --optimized
ss -lntp | grep -E '8080|9000'
아래와 같이 나오면 정상

LISTEN 0      4096     [::ffff:127.0.0.1]:8080             *:*    users:(("java",pid=1895,fd=199))
LISTEN 0      4096     [::ffff:127.0.0.1]:9000             *:*    users:(("java",pid=1895,fd=200))
  • Health Check curl http://127.0.0.1:9000/health

  • Ready 확인: curl http://127.0.0.1:9000/health/ready

  • Live 확인: curl http://127.0.0.1:9000/health/live

  • Metrics 확인 curl http://127.0.0.1:9000/metrics

systemd 설정

vi /etc/systemd/system/keycloak.service

[Unit]
Description=Keycloak Identity and Access Management
Documentation=https://www.keycloak.org/
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service

[Service]
Type=simple

User=keycloak
Group=keycloak

WorkingDirectory=/opt/keycloak

# Java
Environment="JAVA_HOME=/opt/jdk"

# JVM Heap
Environment="JAVA_OPTS_KC_HEAP=-Xms1g -Xmx4g"

ExecStart=/opt/keycloak/bin/kc.sh start --optimized

Restart=on-failure
RestartSec=10

TimeoutStartSec=180
TimeoutStopSec=60

LimitNOFILE=65536

# Security hardening
PrivateTmp=true
ProtectHome=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable keycloak
systemctl restart keycloak

### 로그 확인
journalctl -u keycloak
tail -f /var/log/keycloak/keycloak.log

Reverse Proxy 서버 설정

https 인증서 생성

웹서버 인증서 생성

# SSLEngine on
# SSLProtocol -all +TLSv1.2 +TLSv1.3
# SSLCertificateFile    /etc/ssl/certs/keycloak.crt
# SSLCertificateKeyFile /etc/ssl/private/keycloak.key

CERT_DIR="/etc/ssl/certs"
KEY_DIR="/etc/ssl/private"
DOMAIN="localhost"
DAYS_VALID=3650

# 파일명 설정
KEY_FILE="${KEY_DIR}/keycloak.key"
CERT_FILE="${CERT_DIR}/keycloak.crt"

echo "[1/4] 디렉터리 생성 및 확인 중..."
mkdir -p "$CERT_DIR"
mkdir -p "$KEY_DIR"

echo "[2/4] 자체 서명 SSL 인증서 생성 중..."
# OpenSSL 명령어를 통한 비대화형 인증서 생성
openssl req -x509 -nodes -days $DAYS_VALID -newkey rsa:2048 \
  -keyout "$KEY_FILE" \
  -out "$CERT_FILE" \
  -subj "/C=KR/ST=Seoul/L=Seoul/O=Development/OU=IT/CN=${DOMAIN}" \
  -addext "subjectAltName=DNS:${DOMAIN},IP:127.0.0.1"

echo "[3/4] 파일 권한 설정 중..."
# 개인키 파일은 root만 읽을 수 있도록 제한
chmod 600 "$KEY_FILE"
chmod 644 "$CERT_FILE"

Reverse Proxy 서버로 Nginx를 사용한 방법 (같은 서버)

Nginx 설치

[RockyLinux] dnf install -y nginx

[Debian/Ubuntu] apt install -y nginx

systemctl enable nginx

Nginx Reverse Proxy 설정

[RockyLinux] vi /etc/nginx/conf.d/keycloak.conf

[Debian/Ubuntu] vi /etc/nginx/sites-available/keycloak.conf

server {
  listen 80;
  server_name sso.example.com;

  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  server_name sso.example.com;

  ssl_certificate     /etc/ssl/certs/keycloak.crt;
  ssl_certificate_key /etc/ssl/private/keycloak.key;

  ssl_protocols TLSv1.2 TLSv1.3;

  client_max_body_size 20m;

  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port 443;
    proxy_connect_timeout 30s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
  }
}

[Debian/Ubuntu] 사이트 활성화

rm -f /etc/nginx/sites-enabled/keycloak.conf
ln -s /etc/nginx/sites-available/keycloak.conf /etc/nginx/sites-enabled/keycloak.conf

Nginx 설정 검증

nginx -t

접속 테스트

  • https://sso.example.com
  • 외부에는 https(443/tcp) 외 포트는 개방하지 않는다.

Reverse Proxy 서버로 apache2를 사용한 방법 (다른 서버)

  • 10.10.10.1(apache2) → 10.10.10.143(keycloak)

apache conf 작성

vi /etc/apache2/sites-available/sso.example.com.conf

<VirtualHost _default_:443>
  ServerName sso.example.com

  # =====================================================
  # TLS
  # =====================================================
  SSLEngine on
  SSLCertificateFile  "/etc/ssl/example.com/fullchain.pem"
  SSLCertificateKeyFile   "/etc/ssl/example.com/privkey.pem"
  SSLProtocol -all +TLSv1.2 +TLSv1.3

  # =====================================================
  # Reverse Proxy
  # =====================================================
  ProxyRequests Off
  ProxyPreserveHost On
  ProxyPass        / http://10.10.10.143:8080/
  ProxyPassReverse / http://10.10.10.143:8080/

  # =====================================================
  # Forwarded Headers
  # =====================================================
  RequestHeader set X-Forwarded-Proto "https"
  RequestHeader set X-Forwarded-Port "443"
  RequestHeader set X-Forwarded-Host "sso.example.com"

  # =====================================================
  # Timeout
  # =====================================================
  ProxyTimeout 60

  # =====================================================
  # Logs
  # =====================================================
  ErrorLog ${APACHE_LOG_DIR}/sso.example.com-error.log
  CustomLog ${APACHE_LOG_DIR}/sso.example.com-access.log combined
</VirtualHost>
a2ensite sso.example.com.conf

Keycloak 기본 설정

설정 방법

realm(영역) 생성

  • Manage realms > Create realm
    • Realm name: myrealm (realm 명은 한글 금지, 공백 금지)
    • Enabled: On 이제 모든 설정은 “myrealm” realm 에서 진행 된다.

비밀번호 정책 설정

  • Authentication > Policies > Password policy
    • Minimum length: 10
    • Uppercase characters: 1
    • Lowercase characters: 1
    • Digits: 1
    • Special characters: 1
    • Not username: ON

Brute Force Detection 활성화

  • Realm settings > Security defenses > Brute force detection
    • Brute Force Mode: Lockout temporarily
    • Max login failures: 5
    • Failure reset time: 5 Hours

이메일 설정

  • Realm settings > Email
  • Template 적당히 설정
  • Connection & Authentication
    • Host: 127.0.0.1
    • Allow UTF-8: Enabled 나머지는 모두 기본값 또는 OFF

사용자 직접 가입 비활성화

  • Realm settings > Login
  • Login screen customization: 아래 외 나머지는 모두 OFF
    • Forgot password: ON (Realm settings > Email 설정 필요)
    • Remember me: ON
  • Email settings: 아래 외 나머지는 모두 OFF
    • Email as username: ON (이메일을 ID로 사용)
    • Login with email: ON (이메일로 로그인 가능)
  • User info settings: 모두 OFF

그룹 생성

  • Groups > Create group
  • Name: employees

사용자 생성

  • Users > Create new user
  • Email verified: ON (이메일을 신뢰함)
  • Username: myid@example.com
  • Email: myid@example.com
  • First name: 길동
  • Last name: 홍
  • Group: Join Groups > employees 선택
  • Create > Save

비밀번호 설정

  • Users > 사용자 선택
  • Credentials > Set Password
  • Temporary = OFF (ON으로 설정하면 초기 로그인 후 비밀번호 변경해야 함)

사용자 로그인 테스트

https://sso.example.com/realms/myrealm/account/

SAML 2.0 지원 여부 확인

https://sso.example.com/realms/myrealm/protocol/saml/descriptor

OIDC(OpenID Connect) 1.0 지원 여부 확인

https://sso.example.com/realms/myrealm/.well-known/openid-configuration

OAuth 2.0/OIDC의 기능

OAuth 2.0
   |
   | Authorization Framework
   |
   +--> "이 애플리케이션에 어떤 권한을 줄 것인가?"

OIDC
   |
   | OAuth 2.0 위에 Identity 계층 추가
   |
   +--> "이 사용자가 누구인가?"

[OAuth 2.0]
Access Token
     ↓
API 접근 권한

[OIDC]
ID Token
     ↓
사용자 인증 정보
     ↓
email
name
sub
...
위로 스크롤