Keycloak trên production: HA, LDAP, admin API

4 câu hỏi~30 phútTừ khóa: Infinispan, cluster, KC_HOSTNAME, KC_PROXY_HEADERS, admin REST API, keycloak-admin-client, user federation, theme, metrics

Tiếp đi: đưa Keycloak của shop-app lên production.

Bài 12.2 chạy start-dev trên laptop. Production cần: không chết khi một node chết, đứng sau Nginx đúng hostname, tạo user bằng code, nối LDAP công ty, theo dõi được.

1. Bài toán

Keycloak chết là không ai login được, cả web, mobile, admin. Đây là thành phần quan trọng nhất của shop-app.

2. Vấn đề

3. Giải pháp

Chủ đềCách làm
HA≥ 2 node, chung một DB; cache Infinispan nhúng tự tạo cluster (JGroups, mặc định stack jdbc-ping từ 26)
DatabasePostgres có HA (managed hoặc replica), backup định kỳ
Hostname/proxyKC_HOSTNAME, KC_PROXY_HEADERS=xforwarded, KC_HTTP_ENABLED khi TLS kết thúc ở proxy
AdminTách KC_HOSTNAME_ADMIN nội bộ, chặn /admin ở proxy
User provisioningAdmin REST API qua keycloak-admin-client với service account
User federationLDAP/AD provider; hoặc brokering OIDC/SAML tới Entra ID
Social loginIdentity providers → Google, GitHub, Apple
ThemeTheme login riêng (JAR trong /opt/keycloak/providers hoặc thư mục themes)
MonitoringKC_METRICS_ENABLED, KC_HEALTH_ENABLED, port management 9000

4. Code từng bước

Bước 1: Image tối ưu

FROM quay.io/keycloak/keycloak:26.0 AS builder
ENV KC_DB=postgres KC_HEALTH_ENABLED=true KC_METRICS_ENABLED=true
COPY themes/shop /opt/keycloak/themes/shop
RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:26.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]

Bước 2: Biến môi trường mỗi node

KC_DB_URL=jdbc:postgresql://db.internal:5432/keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=${KC_DB_PASSWORD}          # từ secret manager
KC_HOSTNAME=https://auth.shop.example.com
KC_HOSTNAME_ADMIN=https://auth-admin.internal.example.com
KC_PROXY_HEADERS=xforwarded
KC_HTTP_ENABLED=true                       # TLS kết thúc ở Nginx, mạng nội bộ tin cậy

Nginx:

upstream keycloak { server kc1:8080; server kc2:8080; }
server {
  listen 443 ssl;
  server_name auth.shop.example.com;
  location /admin/ { return 403; }               # admin chỉ qua host nội bộ
  location / {
    proxy_pass http://keycloak;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_buffer_size 128k;                       # header/token lớn
    proxy_buffers 4 256k;
  }
}

Bước 3: Tạo user từ order-service bằng keycloak-admin-client. Tạo client shop-provisioner (confidential, service account) chỉ có role realm-management: manage-users.

<dependency>
  <groupId>org.keycloak</groupId>
  <artifactId>keycloak-admin-client</artifactId>
  <version>26.0.0</version>
</dependency>
@Configuration
class KeycloakAdminConfig {
    @Bean
    Keycloak keycloakAdmin(@Value("${keycloak.url}") String url,
                           @Value("${keycloak.provisioner.secret}") String secret) {
        return KeycloakBuilder.builder()
                .serverUrl(url)
                .realm("shop")
                .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
                .clientId("shop-provisioner")
                .clientSecret(secret)
                .build();
    }
}

@Service
class CustomerAccountService {
    private final Keycloak kc;
    CustomerAccountService(Keycloak kc) { this.kc = kc; }

    String createCustomer(String email, long customerId) {
        var user = new UserRepresentation();
        user.setUsername(email);
        user.setEmail(email);
        user.setEnabled(true);
        user.setAttributes(Map.of("customer_id", List.of(String.valueOf(customerId))));
        user.setRequiredActions(List.of("VERIFY_EMAIL", "UPDATE_PASSWORD"));

        try (Response res = kc.realm("shop").users().create(user)) {
            if (res.getStatus() == 409) throw new IllegalStateException("Email đã tồn tại");
            if (res.getStatus() != 201) throw new IllegalStateException("Keycloak: " + res.getStatus());
            return CreatedResponseUtil.getCreatedId(res);
        }
    }
}

Không đặt password hộ user: gửi email required action để user tự đặt.

Bước 4: Kiểm tra

curl -s https://auth.shop.example.com/realms/shop/.well-known/openid-configuration | jq -r .issuer
# phải là https://auth.shop.example.com/realms/shop
curl -s http://kc1:9000/health/ready
curl -s http://kc1:9000/metrics | grep keycloak_

5. Cách hoạt động

                Internet
                   | HTTPS auth.shop.example.com
                   v
          Nginx (TLS, X-Forwarded-*)       mạng nội bộ: auth-admin.internal
             /            \
        kc1:8080 <--Infinispan/JGroups--> kc2:8080
             \            /                 (cache: realm, user, session, login flow)
          Postgres (HA, backup)  ←── nguồn sự thật; session lưu DB (25+)
             |
      LDAP/AD công ty (user federation, chỉ đọc hoặc sync)

Prometheus ──scrape :9000/metrics──> kc1, kc2
order-service ──Admin REST API (service account)──> /admin/realms/shop/users

Upgrade: đọc migration guide từng bản major, backup DB, thử trên staging với bản sao DB. Keycloak tự migrate schema khi khởi động bản mới. Đổi bản minor thường rolling được; major thì lên kế hoạch downtime ngắn hoặc theo hướng dẫn của bản đó. Provider/theme tự viết phải build lại theo bản mới.

6. So sánh: Keycloak và các lựa chọn khác

Tiêu chíKeycloakAuth0 / OktaAWS CognitoMicrosoft Entra IDTự build
Mô hìnhTự host, open sourceSaaSSaaS AWSSaaS MicrosoftCode của mình
Chi phíHạ tầng + người vận hànhTheo MAU, đắt khi lớnRẻ theo MAUTheo license/MAURất đắt (bảo mật)
Tùy biếnRất cao (SPI, theme)Cao (Actions)Hạn chếTrung bìnhTùy ý
Dữ liệuTrong hạ tầng của mìnhỞ vendorỞ AWSỞ MicrosoftCủa mình
Hợp vớiCần tự chủ, on-prem, nhiều tùy biếnMuốn nhanh, ít opsĐã ở AWSNhân viên dùng Microsoft 365Hầu như không bao giờ

7. Khi nào nên dùng / không nên dùng

Nhân viên công ty đã có tài khoản Active Directory. Mình nên dùng LDAP user federation hay brokering sang Entra ID? User nằm ở đâu trong mỗi cách?

Câu hỏi hay, hai cách khác bản chất:

LDAP user federationIdentity brokering (OIDC/SAML tới Entra ID)
Ai kiểm tra passwordKeycloak bind vào LDAPEntra ID (trang login Microsoft)
User trong KeycloakBản sao/link tới LDAP (import hoặc không)User liên kết, tạo ở lần login đầu
MạngKeycloak phải tới được LDAP nội bộQua Internet, chuẩn OIDC
MFAKeycloak làmEntra ID làm (Conditional Access)
Hợp vớiAD on-premCông ty đã lên Microsoft 365

Với shop-app: khách dùng tài khoản Keycloak + social login Google; nhân viên dùng brokering Entra ID, map group AD sang group /staff/support bằng mapper của identity provider. Mode LDAP nên để READ_ONLY để Keycloak không ghi ngược vào AD.

Mình chạy 2 pod Keycloak trên Kubernetes, login lúc được lúc lỗi "Cookie not found" hoặc "Invalid code". Do cache à?

Rất có thể. Login là chuỗi nhiều request (form, code, token). Nếu hai pod không cùng cluster Infinispan, request sau rơi vào pod không biết authentication session của request trước.

Triển khai Kubernetes chung xem chương Kubernetes.

Phỏng vấn hỏi về Keycloak production và so sánh với Auth0/Cognito thì trả lời sao?

Q1. Chạy Keycloak HA thế nào?

🇻🇳 Tiếng Việt:

Ít nhất hai node start --optimized, chung Postgres có HA, các node tạo cluster Infinispan để chia sẻ cache và authentication session. Đứng sau load balancer với KC_HOSTNAME và proxy headers đúng, admin console chỉ mở nội bộ, theo dõi qua health và metrics.

🇬🇧 English:

At least two nodes running start --optimized, a highly available Postgres, and an Infinispan cluster between nodes to share caches and authentication sessions. They sit behind a load balancer with the right hostname and proxy headers, the admin console is internal only, and we monitor health and metrics.

Q2. Ứng dụng tạo user trong Keycloak thế nào?

🇻🇳 Tiếng Việt:

Dùng Admin REST API qua keycloak-admin-client, xác thực bằng một confidential client có service account chỉ có quyền manage-users. Không đặt password hộ, gắn required action để user tự xác minh email và đặt mật khẩu.

🇬🇧 English:

Through the Admin REST API with keycloak-admin-client, authenticated as a confidential client whose service account only has manage-users. We do not set passwords for users; we add required actions so they verify their email and set a password.

Q3. Keycloak hay Auth0/Cognito/Entra ID?

🇻🇳 Tiếng Việt:

Keycloak khi cần tự host, dữ liệu tự chủ, tùy biến sâu và có người vận hành. SaaS khi muốn nhanh, ít vận hành, chấp nhận chi phí theo MAU. Entra ID cho nhân viên nội bộ dùng Microsoft. Không tự build IdP.

🇬🇧 English:

Keycloak when we need self-hosting, data ownership and deep customization and we have people to operate it. A SaaS provider when we want speed and low ops and accept per-MAU pricing. Entra ID for employees on Microsoft. Never build our own IdP.

Cách nhớ ngắn gọn

Tóm tắt chương 12

BàiÝ chính
12.1 Khái niệmIdP tập trung; realm = tập user, client = app; public vs confidential; realm vs client role; brokering
12.2 DockerCompose + Postgres; start-dev vs start; admin bootstrap; export/--import-realm, secret qua ${ENV}
12.3 Login flowsAuth Code + PKCE cho người; Client Credentials cho service; Device; tắt ROPC; redirect URI và web origins
12.4 Role và tokenClient role → composite → group; mapper thêm claim; audience; Full scope allowed Off; lifetime
12.5 SSO và logoutSSO session cookie; refresh/offline token; RP-initiated và back-channel logout; JWT cũ sống tới exp
12.6 ProductionHA, DB, hostname/proxy, admin API, LDAP/brokering, theme, upgrade, metrics; so với SaaS

Tự kiểm tra

Sau Nginx, issuer trong discovery ra http://10.0.0.5:8080. Sửa gì?

Đặt KC_HOSTNAME và KC_PROXY_HEADERS=xforwarded, Nginx gửi đủ X-Forwarded-*.

Vì sao dùng kc.sh build + --optimized?

Build-time options cố định trong image, khởi động nhanh hơn, không build lại mỗi lần start.

Service account tạo user cần role gì?

realm-management: manage-users (tối thiểu), không phải admin realm.

Hai pod login lúc được lúc lỗi, nghi gì đầu tiên?

Hai pod không cùng cluster Infinispan, authentication session không chia sẻ.

Metrics và health của Keycloak 26 ở port nào?

Management port 9000: /health/ready, /metrics.