Chuyển đến nội dung chính

Bài 13: User Federation - LDAP và Active Directory

Cấu hình LDAP/AD federation, storage mode (READ_ONLY, WRITABLE, UNSYNCED), edit mode, connection settings (SSL, connection pool), LDAP mappers (User Attribute, Full Name, Group, Role, Hardcoded Role, MSAD User Account Control), password hashing, user synchronization, SSSD/FreeIPA integration, Kerberos bridge, custom User Storage SPI và troubleshooting LDAP issues.

🔒 DevSecOps — Bài 13 Bài 13: User Federation - LDAP và Active Directory

Keycloak từ Cơ bản đến Nâng cao

Phần 4: User Federation, Organizations và Authorization

xdev.asia

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ý doGiải thích
Tập trung quản lý userLDAP/AD đã là nguồn user chính trong enterprise → không cần duplicate
Giữ nguyên hệ thống hiện tạiKhông cần migrate user sang Keycloak
Single Source of TruthUser data chỉ tồn tại ở một nơi, tránh inconsistency
Kerberos SSOTích hợp Kerberos authentication từ Active Directory

1.2 Các loại Federation Provider

ProviderMô tả
LDAPHỗ trợ OpenLDAP, 389 Directory Server, và các LDAP-compliant servers
Active DirectoryMicrosoft Active Directory (sử dụng LDAP protocol + AD-specific mappers)
SSSDSystem Security Services Daemon — tích hợp FreeIPA/Red Hat IdM
Custom User Storage SPITự 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

SettingMô tảGiá trị mẫu
Console display nameTên hiển thị trên Admin ConsoleCorporate LDAP
PriorityThứ tự ưu tiên khi có nhiều providers0 (cao nhất)
EnabledBật/tắt providerON
Import usersImport LDAP users vào Keycloak local databaseON

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:

SettingMô tảDefault
Connection poolingBật connection pool để tối ưu hiệu năngON
Connection pool authenticationPool cho authenticated connectionssimple
Connection pool debugLog debug cho connection poolOFF
Connection pool initial sizeSố connections khởi tạo ban đầu1
Connection pool maximum sizeSố connections tối đa1000
Connection pool timeoutThời gian chờ lấy connection từ pool30000 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

SettingMô tảGiá trị mẫu
Users DNBase DN nơi Keycloak tìm kiếm usersou=People,dc=example,dc=com
User Object ClassesLDAP object class cho user entriesinetOrgPerson, organizationalPerson
Username LDAP attributeLDAP attribute chứa usernameuid (LDAP) / sAMAccountName (AD)
RDN LDAP attributeAttribute dùng cho RDN (Relative Distinguished Name)uid (LDAP) / cn (AD)
UUID LDAP attributeAttribute dùng làm unique IDentryUUID (LDAP) / objectGUID (AD)
Search ScopeOne Level hoặc SubtreeSubtree
Custom User LDAP FilterLDAP filter bổ sung để lọc users(&(objectClass=person)(memberOf=cn=app-users,ou=Groups,dc=example,dc=com))
Read TimeoutTimeout cho LDAP read operations30000 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ừ LDAPGhi ngược LDAPImport vào Keycloak DBUse 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 NameLDAP AttributeKeycloak Attribute
usernameuid / sAMAccountNameusername
emailmailemail
first namegivenName / cnfirstName
last namesnlastName
creation datecreateTimestampcreateTimestamp
modify datemodifyTimestampmodifyTimestamp

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:

StrategyMô tả
LOAD_GROUPS_BY_MEMBER_ATTRIBUTELoad 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_RECURSIVELYLoad 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_EXPIREDUser 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:

ScenarioPassword HashLưu ý
READ_ONLY modePassword luôn verify trực tiếp với LDAP serverKeycloak không lưu password hash
WRITABLE modePassword được ghi về LDAP theo LDAP password policyLDAP server thực hiện hashing
UNSYNCED modePassword mới lưu trong Keycloak DB với Keycloak hashingPassword 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ỗiNguyên nhânGiải pháp
javax.naming.CommunicationExceptionKhông kết nối được LDAP serverKiểm tra network, firewall, port 389/636
javax.naming.AuthenticationExceptionSai Bind DN hoặc Bind CredentialVerify bind credentials bằng ldapsearch
SSLHandshakeExceptionCertificate không trustedImport CA cert vào truststore
Connection timeoutLDAP server không responseTă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ỗiNguyên nhânGiải pháp
User ... already existsUsername conflict giữa LDAP và local usersXóa local user hoặc đổi username mapping
Size limit exceededLDAP 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
ReferralLDAP 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