Keycloak trên production: HA, LDAP, admin API
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 đề
- Một node: SPOF.
- Sau proxy TLS, Keycloak nghĩ mình là
http://10.0.0.5:8080: issuer sai, redirect sai, token bị service từ chối. - Admin console mở ra Internet.
- Đăng ký khách hàng ở
order-servicecần tạo user trong Keycloak.
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) |
| Database | Postgres có HA (managed hoặc replica), backup định kỳ |
| Hostname/proxy | KC_HOSTNAME, KC_PROXY_HEADERS=xforwarded, KC_HTTP_ENABLED khi TLS kết thúc ở proxy |
| Admin | Tách KC_HOSTNAME_ADMIN nội bộ, chặn /admin ở proxy |
| User provisioning | Admin REST API qua keycloak-admin-client với service account |
| User federation | LDAP/AD provider; hoặc brokering OIDC/SAML tới Entra ID |
| Social login | Identity providers → Google, GitHub, Apple |
| Theme | Theme login riêng (JAR trong /opt/keycloak/providers hoặc thư mục themes) |
| Monitoring | KC_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í | Keycloak | Auth0 / Okta | AWS Cognito | Microsoft Entra ID | Tự build |
|---|---|---|---|---|---|
| Mô hình | Tự host, open source | SaaS | SaaS AWS | SaaS Microsoft | Code của mình |
| Chi phí | Hạ tầng + người vận hành | Theo MAU, đắt khi lớn | Rẻ theo MAU | Theo license/MAU | Rất đắt (bảo mật) |
| Tùy biến | Rất cao (SPI, theme) | Cao (Actions) | Hạn chế | Trung bình | Tùy ý |
| Dữ liệu | Trong hạ tầng của mình | Ở vendor | Ở AWS | Ở Microsoft | Của mình |
| Hợp với | Cần tự chủ, on-prem, nhiều tùy biến | Muốn nhanh, ít ops | Đã ở AWS | Nhân viên dùng Microsoft 365 | Hầu như không bao giờ |
7. Khi nào nên dùng / không nên dùng
- Nên: có team vận hành, cần dữ liệu tự chủ, nhiều tùy biến, nhiều realm/tenant.
- Không nên: team nhỏ không ai trực Keycloak lúc 3 giờ sáng → dùng SaaS. Đừng tự build IdP.
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 federation | Identity brokering (OIDC/SAML tới Entra ID) | |
|---|---|---|
| Ai kiểm tra password | Keycloak bind vào LDAP | Entra ID (trang login Microsoft) |
| User trong Keycloak | Bản sao/link tới LDAP (import hoặc không) | User liên kết, tạo ở lần login đầu |
| Mạng | Keycloak phải tới được LDAP nội bộ | Qua Internet, chuẩn OIDC |
| MFA | Keycloak làm | Entra ID làm (Conditional Access) |
| Hợp với | AD on-prem | Cô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.
- Kiểm tra log có dòng cluster view gồm cả 2 node.
- Trên Kubernetes dùng Keycloak Operator, hoặc cache stack
kubernetes(DNS_PING qua headless service), hoặcjdbc-pingmặc định của 26. - Mở port JGroups (7800) giữa các pod.
- Sticky session ở ingress giúp giảm nhảy node, nhưng không thay được cluster cache.
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ớiKC_HOSTNAMEvà 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
- HA = ≥ 2 node + DB HA + cluster Infinispan.
- Sau proxy:
KC_HOSTNAME+KC_PROXY_HEADERS; admin nội bộ. - Tạo user bằng admin client + service account quyền tối thiểu.
- LDAP = Keycloak bind; brokering = IdP ngoài login.
Tóm tắt chương 12
| Bài | Ý chính |
|---|---|
| 12.1 Khái niệm | IdP tập trung; realm = tập user, client = app; public vs confidential; realm vs client role; brokering |
| 12.2 Docker | Compose + Postgres; start-dev vs start; admin bootstrap; export/--import-realm, secret qua ${ENV} |
| 12.3 Login flows | Auth Code + PKCE cho người; Client Credentials cho service; Device; tắt ROPC; redirect URI và web origins |
| 12.4 Role và token | Client role → composite → group; mapper thêm claim; audience; Full scope allowed Off; lifetime |
| 12.5 SSO và logout | SSO session cookie; refresh/offline token; RP-initiated và back-channel logout; JWT cũ sống tới exp |
| 12.6 Production | HA, 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.