1. User Federation — Tổng quan
User Federation cho phép Keycloak kết nối với external user databases như LDAP, Active Directory, hoặc custom database. Thay vì phải import toàn bộ users vào Keycloak, bạn có thể authenticate trực tiếp từ nguồn bên ngoài.
Để cấu hình User Federation, vào Admin Console → User Federation.
1.1 Tại sao cần User Federation?
| Lý do | Giải thích |
|---|---|
| Tập trung quản lý user | LDAP/AD đã là nguồn user chính trong enterprise → không cần duplicate |
| Giữ nguyên hệ thống hiện tại | Không cần migrate user sang Keycloak |
| Single Source of Truth | User data chỉ tồn tại ở một nơi, tránh inconsistency |
| Kerberos SSO | Tích hợp Kerberos authentication từ Active Directory |
1.2 Các loại Federation Provider
| Provider | Mô tả |
|---|---|
| LDAP | Hỗ trợ OpenLDAP, 389 Directory Server, và các LDAP-compliant servers |
| Active Directory | Microsoft Active Directory (sử dụng LDAP protocol + AD-specific mappers) |
| SSSD | System Security Services Daemon — tích hợp FreeIPA/Red Hat IdM |
| Custom User Storage SPI | Tự viết provider kết nối bất kỳ database nào |
2. Thêm LDAP Provider
Vào Admin Console → User Federation → Add LDAP providers.
2.1 General Options
| Setting | Mô tả | Giá trị mẫu |
|---|---|---|
| Console display name | Tên hiển thị trên Admin Console | Corporate LDAP |
| Priority | Thứ tự ưu tiên khi có nhiều providers | 0 (cao nhất) |
| Enabled | Bật/tắt provider | ON |
| Import users | Import LDAP users vào Keycloak local database | ON |
2.2 Connection Settings
# Connection URL
Connection URL: ldap://ldap.example.com:389
# Hoặc LDAPS (SSL):
Connection URL: ldaps://ldap.example.com:636
# Bind Type
Bind Type: simple
# Bind DN — tài khoản để Keycloak kết nối LDAP
Bind DN: cn=admin,dc=example,dc=com
# Bind Credential — mật khẩu
Bind Credential: ********
Connection Pool Settings:
| Setting | Mô tả | Default |
|---|---|---|
| Connection pooling | Bật connection pool để tối ưu hiệu năng | ON |
| Connection pool authentication | Pool cho authenticated connections | simple |
| Connection pool debug | Log debug cho connection pool | OFF |
| Connection pool initial size | Số connections khởi tạo ban đầu | 1 |
| Connection pool maximum size | Số connections tối đa | 1000 |
| Connection pool timeout | Thời gian chờ lấy connection từ pool | 30000 ms |
2.3 SSL/LDAPS Configuration
Để kết nối LDAPS (port 636), bạn cần import CA certificate vào Keycloak truststore:
# Tải CA certificate từ LDAP server
openssl s_client -connect ldap.example.com:636 -showcerts < /dev/null 2>/dev/null | \
openssl x509 -outform PEM > ldap-ca.pem
# Import vào Java truststore
keytool -import -alias ldap-ca \
-keystore /opt/keycloak/conf/truststore.jks \
-file ldap-ca.pem \
-storepass changeit -noprompt
# Hoặc sử dụng PEM truststore (Keycloak 24+)
# Đặt file PEM vào /opt/keycloak/conf/truststores/
cp ldap-ca.pem /opt/keycloak/conf/truststores/
Cấu hình Keycloak sử dụng truststore:
# keycloak.conf
# Java keystore
spi-truststore-file-file=/opt/keycloak/conf/truststore.jks
spi-truststore-file-password=changeit
spi-truststore-file-type=JKS
# Hoặc PEM directory (Keycloak 24+)
truststore-paths=/opt/keycloak/conf/truststores
2.4 Use StartTLS
Thay vì LDAPS (port 636), bạn có thể dùng StartTLS trên port 389:
Connection URL: ldap://ldap.example.com:389
Use StartTLS: ON
StartTLS upgrade kết nối LDAP thường thành encrypted connection trên cùng port 389.
3. LDAP Searching Settings
| Setting | Mô tả | Giá trị mẫu |
|---|---|---|
| Users DN | Base DN nơi Keycloak tìm kiếm users | ou=People,dc=example,dc=com |
| User Object Classes | LDAP object class cho user entries | inetOrgPerson, organizationalPerson |
| Username LDAP attribute | LDAP attribute chứa username | uid (LDAP) / sAMAccountName (AD) |
| RDN LDAP attribute | Attribute dùng cho RDN (Relative Distinguished Name) | uid (LDAP) / cn (AD) |
| UUID LDAP attribute | Attribute dùng làm unique ID | entryUUID (LDAP) / objectGUID (AD) |
| Search Scope | One Level hoặc Subtree | Subtree |
| Custom User LDAP Filter | LDAP filter bổ sung để lọc users | (&(objectClass=person)(memberOf=cn=app-users,ou=Groups,dc=example,dc=com)) |
| Read Timeout | Timeout cho LDAP read operations | 30000 ms |
3.1 Active Directory Settings
Khi chọn Vendor = Active Directory, Keycloak tự động cấu hình các giá trị phù hợp:
Username LDAP attribute: cn
RDN LDAP attribute: cn
UUID LDAP attribute: objectGUID
User Object Classes: person, organizationalPerson, user
Users DN: cn=Users,dc=corp,dc=example,dc=com
4. Storage Modes
Keycloak hỗ trợ 3 storage modes quy định cách Keycloak tương tác với LDAP:
| Mode | Đọc từ LDAP | Ghi ngược LDAP | Import vào Keycloak DB | Use case |
|---|---|---|---|---|
| READ_ONLY | ✅ | ❌ | ✅ (cache) | LDAP là nguồn duy nhất, không cho phép user thay đổi thông tin qua Keycloak |
| WRITABLE | ✅ | ✅ | ✅ | Cho phép user thay đổi thông tin (password, profile) và ghi ngược về LDAP |
| UNSYNCED | ✅ | ❌ | ✅ | Import users từ LDAP, sau đó thay đổi chỉ lưu trong Keycloak DB (không ghi ngược) |
4.1 Edit Modes
Edit Mode quy định hành vi khi user hoặc admin thay đổi thông tin:
READ_ONLY:
- User không thể đổi password qua Keycloak
- Admin không thể edit user attributes
- Mọi thay đổi phải thực hiện trực tiếp trên LDAP
WRITABLE:
- User có thể đổi password → Keycloak ghi ngược về LDAP
- Admin edit user attributes → cập nhật LDAP
- Cẩn thận với password policy: phải match giữa Keycloak và LDAP
UNSYNCED:
- User đổi password → chỉ lưu trong Keycloak DB
- Đăng nhập: Keycloak thử password local trước, nếu fail thì thử LDAP
- Phù hợp khi muốn dần migrate users sang Keycloak
5. Sync Settings
Keycloak có thể đồng bộ users từ LDAP theo 2 cơ chế:
5.1 Periodic Full Sync
# Import toàn bộ users từ LDAP vào Keycloak DB
Periodic Full Sync: ON
Full Sync Period: 604800 # seconds (7 ngày)
5.2 Periodic Changed Users Sync
# Chỉ đồng bộ users có thay đổi (dựa vào modifyTimestamp)
Periodic Changed Users Sync: ON
Changed Users Sync Period: 86400 # seconds (1 ngày)
5.3 Manual Sync
Bạn có thể trigger sync thủ công từ Admin Console hoặc qua CLI:
# Trigger full sync qua Admin REST API
curl -X POST "http://localhost:8080/admin/realms/my-realm/user-storage/${LDAP_PROVIDER_ID}/sync?action=triggerFullSync" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
# Trigger changed users sync
curl -X POST "http://localhost:8080/admin/realms/my-realm/user-storage/${LDAP_PROVIDER_ID}/sync?action=triggerChangedUsersSync" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
6. LDAP Mappers
LDAP Mappers định nghĩa cách Keycloak map LDAP attributes sang Keycloak user model. Đây là phần quan trọng nhất khi cấu hình LDAP federation.
6.1 user-attribute-ldap-mapper
Map một LDAP attribute sang một Keycloak user attribute:
Mapper Type: user-attribute-ldap-mapper
LDAP Attribute: mail
User Model Attribute: email
Read Only: true
Always Read Value From LDAP: false
Is Mandatory In LDAP: true
Các mappers mặc định được tạo tự động:
| Mapper Name | LDAP Attribute | Keycloak Attribute |
|---|---|---|
| username | uid / sAMAccountName | username |
mail | email | |
| first name | givenName / cn | firstName |
| last name | sn | lastName |
| creation date | createTimestamp | createTimestamp |
| modify date | modifyTimestamp | modifyTimestamp |
6.2 full-name-ldap-mapper
Map LDAP cn (Common Name) sang Keycloak firstName + lastName:
Mapper Type: full-name-ldap-mapper
LDAP Full Name Attribute: cn
Read Only: true
Write Only: false
Hữu ích khi LDAP chỉ có cn mà không tách givenName/sn.
6.3 group-ldap-mapper
Đồng bộ LDAP groups sang Keycloak groups:
Mapper Type: group-ldap-mapper
LDAP Groups DN: ou=Groups,dc=example,dc=com
Group Name LDAP Attribute: cn
Group Object Classes: groupOfNames
Membership LDAP Attribute: member
Membership Attribute Type: DN
Membership User LDAP Attribute: uid
Mode: READ_ONLY
User Groups Retrieve Strategy: LOAD_GROUPS_BY_MEMBER_ATTRIBUTE
Drop non-existing groups during sync: false
Groups Path: /
User Groups Retrieve Strategy options:
| Strategy | Mô tả |
|---|---|
| LOAD_GROUPS_BY_MEMBER_ATTRIBUTE | Load groups từ LDAP dựa vào member attribute |
| GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE | Đọc memberOf attribute trên user entry |
| LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY | Load groups đệ quy (nested groups) |
6.4 role-ldap-mapper
Đồng bộ LDAP roles/groups sang Keycloak realm roles:
Mapper Type: role-ldap-mapper
LDAP Roles DN: ou=Roles,dc=example,dc=com
Role Name LDAP Attribute: cn
Role Object Classes: groupOfNames
Membership LDAP Attribute: member
Membership Attribute Type: DN
Membership User LDAP Attribute: uid
Mode: READ_ONLY
Use Realm Roles Mapping: true
Client ID: (để trống nếu dùng realm roles)
6.5 hardcoded-ldap-role-mapper
Tự động gán một role cố định cho tất cả users từ LDAP provider:
Mapper Type: hardcoded-ldap-role-mapper
Role: realm-role-name
# Hoặc client role:
Role: client-id.client-role-name
Hữu ích khi muốn phân biệt users từ LDAP với local users bằng một role marker.
6.6 msad-user-account-control-mapper
Mapper đặc biệt cho Active Directory, xử lý userAccountControl attribute:
Mapper Type: msad-user-account-control-mapper
# Xử lý:
# - Account enabled/disabled status
# - Password expired status
# - Account locked status
# - Require user to change password at next login
Mapper này đọc bitmask userAccountControl của AD để map sang Keycloak user status:
| AD Flag (bit) | Keycloak Behavior |
|---|---|
ACCOUNTDISABLE (0x0002) | User bị disabled trong Keycloak |
LOCKOUT (0x0010) | User bị locked |
PASSWORD_EXPIRED | User phải đổi password khi đăng nhập |
6.7 certificate-ldap-mapper
Map LDAP certificate attribute sang Keycloak user attribute cho X.509 authentication:
Mapper Type: certificate-ldap-mapper
LDAP Attribute: userCertificate
User Model Attribute: usercertificate
Is DER Formatted: true
Always Read Value From LDAP: true
7. Password Hashing
Khi sử dụng LDAP federation, password hashing có một số đặc điểm quan trọng:
| Scenario | Password Hash | Lưu ý |
|---|---|---|
| READ_ONLY mode | Password luôn verify trực tiếp với LDAP server | Keycloak không lưu password hash |
| WRITABLE mode | Password được ghi về LDAP theo LDAP password policy | LDAP server thực hiện hashing |
| UNSYNCED mode | Password mới lưu trong Keycloak DB với Keycloak hashing | Password cũ vẫn verify qua LDAP |
# Kiểm tra password policy trên LDAP (OpenLDAP)
ldapsearch -x -H ldap://localhost:389 \
-D "cn=admin,dc=example,dc=com" -W \
-b "cn=config" "(objectClass=olcGlobal)" olcPasswordHash
# Output ví dụ:
# olcPasswordHash: {SSHA}
8. SSSD và FreeIPA Integration
Keycloak hỗ trợ tích hợp với SSSD (System Security Services Daemon) thông qua D-Bus interface, cho phép authenticate users từ FreeIPA hoặc Red Hat Identity Manager.
8.1 Prerequisites
# Cài đặt SSSD trên Keycloak server
sudo dnf install sssd sssd-dbus
# Cấu hình SSSD (/etc/sssd/sssd.conf)
[sssd]
services = nss, pam, ifp
domains = example.com
[domain/example.com]
id_provider = ipa
auth_provider = ipa
access_provider = ipa
ipa_domain = example.com
ipa_server = ipa.example.com
[ifp]
allowed_uids = root, keycloak
user_attributes = +mail, +givenname, +sn, +telephoneNumber
8.2 Cấu hình SSSD Federation Provider
Trong Admin Console, thêm SSSD federation provider — Keycloak sẽ giao tiếp với SSSD qua D-Bus để:
- Authenticate users (PAM)
- Lấy user attributes (InfoPipe)
- Lấy group membership
9. Kerberos Bridge
Keycloak có thể sử dụng Kerberos authentication cùng với LDAP federation, cho phép users đăng nhập tự động bằng Kerberos ticket (SPNEGO).
9.1 Cấu hình Kerberos với LDAP
# Trong LDAP provider settings
Allow Kerberos authentication: ON
Kerberos Realm: EXAMPLE.COM
Server Principal: HTTP/[email protected]
KeyTab: /etc/keycloak/keycloak.keytab
Use Kerberos for password authentication: ON
# Tạo keytab cho Keycloak service principal
kadmin.local -q "addprinc -randkey HTTP/[email protected]"
kadmin.local -q "ktadd -k /etc/keycloak/keycloak.keytab HTTP/[email protected]"
# Set permissions
chown keycloak:keycloak /etc/keycloak/keycloak.keytab
chmod 600 /etc/keycloak/keycloak.keytab
9.2 Browser Configuration cho SPNEGO
Firefox:
1. about:config
2. network.negotiate-auth.trusted-uris = .example.com
3. network.negotiate-auth.delegation-uris = .example.com
Chrome / Edge:
1. Policy: AuthServerAllowlist = *.example.com
2. Hoặc command line: --auth-server-whitelist="*.example.com"
10. Custom User Storage SPI
Khi LDAP không đủ, bạn có thể viết Custom User Storage Provider để kết nối bất kỳ data source nào (SQL database, REST API, legacy system...).
10.1 SPI Interfaces
// UserStorageProviderFactory — tạo provider instances
public class MyUserStorageProviderFactory
implements UserStorageProviderFactory<MyUserStorageProvider> {
@Override
public String getId() {
return "my-user-storage";
}
@Override
public MyUserStorageProvider create(KeycloakSession session,
ComponentModel model) {
return new MyUserStorageProvider(session, model);
}
}
// UserStorageProvider — implement các interfaces cần thiết
public class MyUserStorageProvider implements
UserStorageProvider,
UserLookupProvider,
CredentialInputValidator,
UserQueryProvider {
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
// Query external database
ExternalUser extUser = externalDb.findByUsername(username);
if (extUser == null) return null;
// Wrap vào Keycloak UserModel
return new UserAdapter(session, realm, model, extUser);
}
@Override
public boolean isValid(RealmModel realm, UserModel user,
CredentialInput input) {
if (!supportsCredentialType(input.getType())) return false;
// Verify password với external system
return externalDb.verifyPassword(
user.getUsername(),
input.getChallengeResponse()
);
}
}
10.2 Deploy Custom Provider
# Build JAR
mvn clean package
# Copy vào Keycloak providers directory
cp target/my-user-storage.jar /opt/keycloak/providers/
# Rebuild Keycloak
/opt/keycloak/bin/kc.sh build
11. Cấu hình LDAP với kcadm.sh
Sử dụng kcadm.sh để cấu hình LDAP federation qua command line:
# Đăng nhập
kcadm.sh config credentials \
--server http://localhost:8080 \
--realm master \
--user admin \
--password admin
# Tạo LDAP provider
kcadm.sh create components -r my-realm \
-s name="Corporate LDAP" \
-s providerId=ldap \
-s providerType=org.keycloak.storage.UserStorageProvider \
-s 'config.vendor=["other"]' \
-s 'config.connectionUrl=["ldap://ldap.example.com:389"]' \
-s 'config.bindDn=["cn=admin,dc=example,dc=com"]' \
-s 'config.bindCredential=["admin_password"]' \
-s 'config.usersDn=["ou=People,dc=example,dc=com"]' \
-s 'config.userObjectClasses=["inetOrgPerson, organizationalPerson"]' \
-s 'config.usernameLDAPAttribute=["uid"]' \
-s 'config.rdnLDAPAttribute=["uid"]' \
-s 'config.uuidLDAPAttribute=["entryUUID"]' \
-s 'config.editMode=["READ_ONLY"]' \
-s 'config.syncRegistrations=["false"]' \
-s 'config.searchScope=["2"]' \
-s 'config.importEnabled=["true"]' \
-s 'config.enabled=["true"]' \
-s 'config.priority=["0"]' \
-s 'config.fullSyncPeriod=["604800"]' \
-s 'config.changedSyncPeriod=["86400"]'
# Lấy LDAP provider ID
LDAP_ID=$(kcadm.sh get components -r my-realm \
--fields id,name \
-q providerType=org.keycloak.storage.UserStorageProvider \
| jq -r '.[0].id')
# Thêm group mapper
kcadm.sh create components -r my-realm \
-s name="group-mapper" \
-s providerId=group-ldap-mapper \
-s providerType=org.keycloak.storage.ldap.mappers.LDAPStorageMapper \
-s parentId=$LDAP_ID \
-s 'config.groups.dn=["ou=Groups,dc=example,dc=com"]' \
-s 'config.group.name.ldap.attribute=["cn"]' \
-s 'config.group.object.classes=["groupOfNames"]' \
-s 'config.membership.ldap.attribute=["member"]' \
-s 'config.membership.attribute.type=["DN"]' \
-s 'config.membership.user.ldap.attribute=["uid"]' \
-s 'config.mode=["READ_ONLY"]' \
-s 'config.drop.non.existing.groups.during.sync=["false"]'
# Trigger full sync
kcadm.sh create user-storage/$LDAP_ID/sync -r my-realm \
-s action=triggerFullSync
12. Troubleshooting LDAP Issues
12.1 Connection Issues
| Lỗi | Nguyên nhân | Giải pháp |
|---|---|---|
javax.naming.CommunicationException | Không kết nối được LDAP server | Kiểm tra network, firewall, port 389/636 |
javax.naming.AuthenticationException | Sai Bind DN hoặc Bind Credential | Verify bind credentials bằng ldapsearch |
SSLHandshakeException | Certificate không trusted | Import CA cert vào truststore |
Connection timeout | LDAP server không response | Tăng connection timeout, kiểm tra DNS |
# Test LDAP connection
ldapsearch -x -H ldap://ldap.example.com:389 \
-D "cn=admin,dc=example,dc=com" -W \
-b "ou=People,dc=example,dc=com" \
"(objectClass=inetOrgPerson)" uid mail cn
# Test LDAPS connection
ldapsearch -x -H ldaps://ldap.example.com:636 \
-D "cn=admin,dc=example,dc=com" -W \
-b "dc=example,dc=com" "(uid=testuser)"
# Bật debug logging trong Keycloak
bin/kc.sh start-dev \
--log-level=org.keycloak.storage.ldap:DEBUG
12.2 Sync Failures
| Lỗi | Nguyên nhân | Giải pháp |
|---|---|---|
User ... already exists | Username conflict giữa LDAP và local users | Xóa local user hoặc đổi username mapping |
Size limit exceeded | LDAP server giới hạn số kết quả trả về | Cấu hình paging trên LDAP server, hoặc thêm LDAP filter để giảm scope |
Referral | LDAP trả về referral thay vì kết quả | Set Referral = follow trong connection settings |
12.3 Mapper Problems
# Kiểm tra LDAP attributes có tồn tại
ldapsearch -x -H ldap://ldap.example.com:389 \
-D "cn=admin,dc=example,dc=com" -W \
-b "uid=testuser,ou=People,dc=example,dc=com" \
"*" "+"
# Kiểm tra group membership
ldapsearch -x -H ldap://ldap.example.com:389 \
-D "cn=admin,dc=example,dc=com" -W \
-b "ou=Groups,dc=example,dc=com" \
"(member=uid=testuser,ou=People,dc=example,dc=com)" cn
13. Best Practices
- Luôn dùng LDAPS hoặc StartTLS — tránh gửi credentials dạng plaintext
- Sử dụng service account riêng cho Bind DN — không dùng admin account
- Giới hạn Search Scope — dùng Custom User LDAP Filter để chỉ import users cần thiết
- Bật Connection Pool — giảm overhead tạo connection
- Cấu hình sync period phù hợp — quá ngắn gây load nặng trên LDAP, quá dài gây stale data
- Monitor sync logs — Keycloak ghi log chi tiết về sync process
- Test với READ_ONLY trước — khi mới cấu hình, dùng READ_ONLY để verify trước khi chuyển WRITABLE
- Backup Keycloak DB trước khi sync lớn — full sync có thể import hàng nghìn users