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

Lesson 13: User Federation - LDAP and Active Directory

Configure 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 and troubleshooting LDAP issues.

🔒 DevSecOps — Lesson 13 Lesson 13: User Federation - LDAP and Active Directory

Keycloak from Basic to Advanced

Part 4: User Federation, Organizations and Authorization

xdev.asia

1. User Federation — Overview

User Federation allows Keycloak to connect to external user databases such as LDAP, Active Directory, or custom databases. Instead of having to import all users into Keycloak, you can authenticate directly from an external source.

To configure User Federation, go to Admin Console → User Federation.

1.1 Why do we need User Federation?

ReasonExplanation
Centralize user managementLDAP/AD is already the main source of users in the enterprise → no need to duplicate
Keep the current system intactNo need to migrate users to Keycloak
Single Source of TruthUser data only exists in one place, avoid inconsistency
Kerberos SSOIntegrating Kerberos authentication from Active Directory

1.2 Types of Federation Provider

ProviderDescription
LDAPSupports OpenLDAP, 389 Directory Server, and LDAP-compliant servers
Active DirectoryMicrosoft Active Directory (using LDAP protocol + AD-specific mappers)
SSSDSystem Security Services Daemon — FreeIPA/Red Hat IdM integration
Custom User Storage SPIWrite your own provider to connect any database

2. Add LDAP Provider

Go to Admin Console → User Federation → Add LDAP providers.

2.1 General Options

SettingDescriptionSample value
Console display nameDisplay name on Admin ConsoleCorporate LDAP
PriorityPriority order when there are many providers0 (highest)
EnabledEnable/disable providerON
Import usersImport LDAP users into 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:

SettingDescriptionDefault
Connection poolingTurn on connection pooling to optimize performanceON
Connection pool authenticationPool cho authenticated connectionssimple
Connection pool debugLog debug cho connection poolOFF
Connection pool initial sizeNumber of initial initial connections1
Connection pool maximum sizeMaximum number of connections1000
Connection pool timeoutTime to wait for connection from pool30000 ms

2.3 SSL/LDAPS Configuration

To connect to LDAPS (port 636), you need to import CA certificate into 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/

Configure Keycloak using 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

Instead of LDAPS (port 636), you can use StartTLS on port 389:

Connection URL: ldap://ldap.example.com:389
Use StartTLS: ON

StartTLS upgrade regular LDAP connection to encrypted connection on same port 389.

3. LDAP Searching Settings

SettingDescriptionSample value
Users DNBase DN where Keycloak looks for usersou=People,dc=example,dc=com
User Object ClassesLDAP object class cho user entriesinetOrgPerson, organizationalPerson
Username LDAP attributeLDAP attribute contains usernameuid (LDAP) / sAMAccountName (AD)
RDN LDAP attributeAttribute used for RDN (Relative Distinguished Name)uid (LDAP) / cn (AD)
UUID LDAP attributeAttribute used as unique IDentryUUID (LDAP) / objectGUID (AD)
Search ScopeOne Level or SubtreeSubtree
Custom User LDAP FilterLDAP additional filter to filter 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

When selecting Vendor = Active Directory, Keycloak automatically configures the appropriate values:

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 supports 3 storage modes that dictate how Keycloak interacts with LDAP:

ModeRead from LDAPWrite back to LDAPImport to Keycloak DBUse case
READ_ONLY✅❌✅ (cache)LDAP is the only source, does not allow users to change information via Keycloak
WRITABLE✅✅✅Allow users to change information (password, profile) and write back to LDAP
UNSYNCED✅❌✅Import users from LDAP, then save changes only in Keycloak DB (no writeback)

4.1 Edit Modes

Edit Mode regulates the behavior when a user or admin changes information:

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 can synchronize users from LDAP by 2 mechanisms:

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

You can trigger sync manually from Admin Console or via 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 defines how Keycloak map LDAP attributes to Keycloak user model. This is the most important part when configuring LDAP federation.

6.1 user-attribute-ldap-mapper

Map an LDAP attribute to a 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

Automatically generated default mappers:

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

Useful when LDAP only has cn without separating givenName/sn.

6.3 group-ldap-mapper

Sync LDAP groups to 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:

StrategyDescription
LOAD_GROUPS_BY_MEMBER_ATTRIBUTELoad groups from LDAP based on member attribute
GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTERead memberOf attribute on user entry
LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELYLoad groups recursively (nested groups)

6.4 role-ldap-mapper

Sync LDAP roles/groups to 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

Automatically assign a fixed role to all users from LDAP provider:

Mapper Type: hardcoded-ldap-role-mapper
Role: realm-role-name
# Hoặc client role:
Role: client-id.client-role-name

Useful when you want to distinguish users from LDAP from local users with a role marker.

6.6 msad-user-account-control-mapper

Special Mapper for Active Directory, handling 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

This Mapper reads bitmask userAccountControl of AD to map to Keycloak user status:

AD Flag (bit)Keycloak Behavior
ACCOUNTDISABLE (0x0002)User is disabled in Keycloak
LOCKOUT (0x0010)User is locked
PASSWORD_EXPIREDUser must change password when logging in

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

When using LDAP federation, password hashing has several important characteristics:

ScenarioPassword HashNote
READ_ONLY modePassword always verifies directly with LDAP serverKeycloak does not save password hash
WRITABLE modePassword is recorded to LDAP according to LDAP password policyLDAP server performs hashing
UNSYNCED modeNew password stored in Keycloak DB with Keycloak hashingOld password still verified via 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 and FreeIPA Integration

Keycloak supports integration with SSSD (System Security Services Daemon) via the D-Bus interface, allowing authenticate users from FreeIPA or 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 Configure SSSD Federation Provider

In Admin Console, add SSSD federation provider — Keycloak will communicate with SSSD via D-Bus to:

  • Authenticate users (PAM)
  • Get user attributes (InfoPipe)
  • Get group membership

9. Kerberos Bridge

Keycloak can use Kerberos authentication in conjunction with LDAP federation, allowing users to log in automatically using a Kerberos ticket (SPNEGO).

9.1 Configuring Kerberos with 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

When LDAP is not enough, you can write Custom User Storage Provider to connect any data source (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. Configure LDAP with kcadm.sh

Use kcadm.sh to configure LDAP federation via 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

ErrorCauseSolution
javax.naming.CommunicationExceptionUnable to connect to LDAP serverCheck network, firewall, port 389/636
javax.naming.AuthenticationExceptionIncorrect Bind DN or Bind CredentialVerify bind credentials with ldapsearch
SSLHandshakeExceptionCertificate not trustedImport CA cert into truststore
Connection timeoutLDAP server does not respondIncrease connection timeout, check 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

ErrorCauseSolution
User ... already existsUsername conflict between LDAP and local usersDelete local user or change username mapping
Size limit exceededLDAP server limits the number of returned resultsConfigure paging on LDAP server, or add LDAP filter to reduce scope
ReferralLDAP returns referral instead of resultSet Referral = follow in 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

  • Always use LDAPS or StartTLS — avoid sending credentials in plaintext
  • Use separate service account for Bind DN — do not use admin account
  • Search Scope Limit — use Custom User LDAP Filter to import only necessary users
  • Enable Connection Pool — reduces connection creation overhead
  • Configure the sync period appropriately — too short causes heavy load on LDAP, too long causes stale data
  • Monitor sync logs — Keycloak records detailed sync process logs
  • Test with READ_ONLY first — when first configuring, use READ_ONLY to verify before transferring WRITABLE
  • Backup Keycloak DB before big sync — full sync can import thousands of users