Skip to content

필사 모드: Keycloak LDAP/Active Directory Integration — A Practical Guide to User Federation

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

When adopting Keycloak in an enterprise environment, the very first challenge you run into is: "What do we do with the user directory we already have?" Most organizations operate a directory service such as Active Directory (AD) or OpenLDAP that has been running for years or even decades, holding thousands to hundreds of thousands of user accounts, groups, and password policies.

As of 2026, Keycloak has evolved to the 26.6.x line (26.6.2 as of May 2026), gaining modern capabilities such as passkeys integrated into the login form, FAPI 2.0 Security Profile Final support, and Workflows for realm management automation. Yet the success of an enterprise rollout is still decided by the quality of integration with the legacy directory. Even in an era where Zero Trust and identity-first security are the default, everything starts from "a single trustworthy user store."

This article covers Keycloak User Federation from architecture to the details of LDAP provider configuration, AD-specific options, attribute and group mapping, performance tuning for large directories, troubleshooting sync failures, and finally a migration strategy for eventually retiring LDAP in favor of Keycloak built-in storage — all from an operational perspective.

Understanding the User Federation Architecture

Basic structure

Keycloak User Federation is the layer that "links" an external user store to the Keycloak user model. The key point is that Keycloak does not replace the external store; instead, it performs delegation and caching through the User Storage SPI.

+-------------------+ +----------------------------+

| Application | | Keycloak |

| (OIDC / SAML) +------->+ +----------------------+ |

+-------------------+ | | Authentication Flow | |

| +----------+-----------+ |

| | |

| +----------v-----------+ |

| | User Cache (L1) | |

| +----------+-----------+ |

| | |

| +----------v-----------+ |

| | User Storage SPI | |

| +---+-------------+----+ |

| | | |

+------+-------------+-------+

| |

+---------v---+ +-----v--------+

| Local DB | | LDAP / AD |

| (federated | | Provider |

| link data) | +-----+--------+

+-------------+ |

+-----v--------+

| Directory |

| (AD/LDAP) |

+--------------+

The flow of operations is as follows.

1. When a user attempts to log in, Keycloak first looks the user up in its internal cache and local DB.

2. If not found, it queries the registered User Storage providers (the LDAP provider) in priority order.

3. If the user is found in LDAP, Keycloak creates a "federated user" entry in its local DB. This entry keeps a link to the LDAP entry (the original DN and provider ID).

4. Password verification is delegated to an LDAP bind or handled internally by Keycloak, depending on the configuration.

The important thing to understand is that **a federated user is a link, not a mirror of the LDAP entry**. Which attributes are copied locally and where write operations go are entirely determined by the edit mode and mapper configuration.

Import mode vs. no-import mode

The `importEnabled` option of the LDAP provider significantly changes operational characteristics.

| Mode | Behavior | Pros | Cons |

| --- | --- | --- | --- |

| import on (default) | Copy users into the local DB and keep a link | Fast lookups/searches, stable offline tokens | Sync required, DB growth |

| no-import | Query LDAP directly on every request | No sync needed, always fresh | Higher LDAP load, some feature limitations |

For large organizations (50,000+ users), import mode plus periodic sync is the norm. No-import works well when the directory is fast and close, the user population is small, and "LDAP must always be the source of truth."

LDAP Provider Configuration in Detail

Basic connection settings

You can configure everything through the Admin Console under User Federation, but for reproducible infrastructure we recommend codifying it with the `kcadm.sh` CLI or Terraform (the keycloak provider).

Create the LDAP provider (kcadm.sh)

./kcadm.sh create components -r myrealm \

-s name=corp-ldap \

-s providerId=ldap \

-s providerType=org.keycloak.storage.UserStorageProvider \

-s 'config.enabled=["true"]' \

-s 'config.priority=["0"]' \

-s 'config.editMode=["READ_ONLY"]' \

-s 'config.vendor=["ad"]' \

-s 'config.connectionUrl=["ldaps://ad01.corp.example.com:636"]' \

-s 'config.usersDn=["OU=Employees,DC=corp,DC=example,DC=com"]' \

-s 'config.bindDn=["CN=svc-keycloak,OU=ServiceAccounts,DC=corp,DC=example,DC=com"]' \

-s 'config.bindCredential=["CHANGE_ME"]' \

-s 'config.usernameLDAPAttribute=["sAMAccountName"]' \

-s 'config.rdnLDAPAttribute=["cn"]' \

-s 'config.uuidLDAPAttribute=["objectGUID"]' \

-s 'config.userObjectClasses=["person, organizationalPerson, user"]' \

-s 'config.searchScope=["2"]' \

-s 'config.useTruststoreSpi=["always"]' \

-s 'config.connectionPooling=["true"]' \

-s 'config.pagination=["true"]'

Key points regarding the connection:

- **Always use LDAPS (636) or StartTLS.** A configuration where the bind password travels over plaintext LDAP (389) will be flagged immediately in any audit.

- Create the service account used for `bindDn` with least privilege (read-only, scoped to the required OUs) and inject the password from a secrets manager such as Vault.

- Use `objectGUID` as `uuidLDAPAttribute` for AD and `entryUUID` for OpenLDAP. This value becomes the immutable key of the federated link; a wrong setting leads to duplicate-user incidents.

Edit Mode — READ_ONLY, WRITABLE, UNSYNCED

The edit mode is the single most important setting: it determines "where write operations go."

| Edit Mode | Profile updates | Password changes | Best-fit scenario |

| --- | --- | --- | --- |

| READ_ONLY | Not allowed (throws) | Not allowed | AD is the sole source of truth, HR system manages AD |

| WRITABLE | Written directly to LDAP | Written to LDAP | Keycloak used as a self-service portal |

| UNSYNCED | Written only to the Keycloak local DB | Stored locally only | LDAP is read-only seed, gradually becoming independent |

Let us look at the operational implications of each mode.

**READ_ONLY** is the safest default. If a user tries to edit their profile in the Keycloak Account Console, an error occurs, so you should disable those features in the Account Console or adjust required actions. Password change requests are rejected, so you will need guidance like "Please change your password in the corporate portal."

**WRITABLE** turns Keycloak into a writing client of LDAP. The bind service account now needs write permissions, and with AD, LDAPS becomes mandatory for password changes (AD refuses unicodePwd modifications over insecure channels). Also note that Keycloak required actions (e.g., forced password update on first login) will change the actual AD password, so conflicts with the AD-side password policy must be reviewed carefully.

**UNSYNCED** is an interesting middle ground. Users are read from LDAP, but subsequent changes are stored only in the Keycloak local store. It is effectively a "gradual migration mode that uses LDAP as seed data" and the core tool of the migration strategy discussed later. Beware that in this mode LDAP and Keycloak data diverge over time, so you must define a clear operational policy about which side is the truth.

Sync strategies — full sync and changed users sync

In import mode, you need synchronization to propagate LDAP changes into the Keycloak local DB.

Trigger a full sync (use the component ID returned at creation time)

./kcadm.sh create user-storage/COMPONENT_ID/sync?action=triggerFullSync -r myrealm

Trigger a changed-users sync

./kcadm.sh create user-storage/COMPONENT_ID/sync?action=triggerChangedUsersSync -r myrealm

Periodic settings live in the provider config.

./kcadm.sh update components/COMPONENT_ID -r myrealm \

-s 'config.fullSyncPeriod=["604800"]' \

-s 'config.changedSyncPeriod=["3600"]'

| Strategy | Recommended period | Behavior | Caveat |

| --- | --- | --- | --- |

| Full sync | Weekly (604800 s) | Re-fetch and refresh all users | Can take tens of minutes on large directories |

| Changed users sync | Hourly (3600 s) | Incremental fetch based on modification timestamps | Cannot detect deletions |

Changed sync fetches only the entries modified since the last run, based on the LDAP `modifyTimestamp` attribute (AD: `whenChanged`). There are two traps people commonly fall into.

1. **Deletions are not detected.** Users deleted in LDAP do not disappear through changed sync. They are removed during full sync (the "periodic full sync should remove non-existent users" behavior) or require a separate cleanup job. Leaver accounts surviving in Keycloak is a classic security incident, so design a deactivation policy (disable in Keycloak when disabled in AD) along with the full sync schedule.

2. **Timestamps are directory-server time.** If the clocks of the Keycloak server and the directory server drift apart, changes can be missed. Verify NTP synchronization.

Active Directory-Specific Configuration

MSAD User Account Control Mapper

Unlike standard LDAP, AD manages account state via a bit-flag attribute called `userAccountControl`. Keycloak's MSAD user account control mapper interprets these flags and links them to Keycloak user state.

Key userAccountControl bit flags

---------------------------------------------

0x0002 ACCOUNTDISABLE account disabled

0x0010 LOCKOUT account locked out

0x0020 PASSWD_NOTREQD no password required

0x10000 DONT_EXPIRE_PASSWD password never expires

0x800000 PASSWORD_EXPIRED password expired

e.g.) 512 (0x200) = normal account

514 (0x202) = normal account, disabled

66048 = normal + password never expires

With this mapper enabled, the following becomes possible.

- Accounts disabled in AD (ACCOUNTDISABLE) are also blocked from logging into Keycloak.

- When AD reports PASSWORD_EXPIRED, Keycloak triggers the UPDATE_PASSWORD required action (in WRITABLE mode).

- Users whose `pwdLastSet` is 0 (must change password at next logon) are also routed into the password-update flow.

Another tip for AD integrations: adjust the search filter so that `computer` objects do not slip in through `userObjectClasses`, because AD applies the user objectClass to computer accounts as well.

Example additional user LDAP filter (custom user LDAP filter)

(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

This filter returns only accounts in the person category whose disabled bit is not set. `1.2.840.113556.1.4.803` is the OID of AD's bitwise-AND matching rule.

Kerberos / SPNEGO integration

To let users who are domain-logged-in on corporate Windows PCs reach Keycloak in the browser without typing a password, configure Kerberos/SPNEGO integration.

First, register an SPN (Service Principal Name) for the Keycloak service account in AD and issue a keytab.

On the AD domain controller (admin privileges)

setspn -A HTTP/sso.corp.example.com svc-keycloak-krb

ktpass -princ HTTP/sso.corp.example.com@CORP.EXAMPLE.COM \

-mapuser CORP\svc-keycloak-krb \

-crypto AES256-SHA1 -ptype KRB5_NT_PRINCIPAL \

-pass SERVICE_ACCOUNT_PASSWORD \

-out keycloak.keytab

On the Keycloak server side, place krb5.conf and enable the Kerberos integration options on the LDAP provider.

/etc/krb5.conf

[libdefaults]

default_realm = CORP.EXAMPLE.COM

dns_lookup_kdc = true

forwardable = true

[realms]

CORP.EXAMPLE.COM = {

kdc = ad01.corp.example.com

admin_server = ad01.corp.example.com

}

./kcadm.sh update components/COMPONENT_ID -r myrealm \

-s 'config.allowKerberosAuthentication=["true"]' \

-s 'config.kerberosRealm=["CORP.EXAMPLE.COM"]' \

-s 'config.serverPrincipal=["HTTP/sso.corp.example.com@CORP.EXAMPLE.COM"]' \

-s 'config.keyTab=["/opt/keycloak/conf/keycloak.keytab"]' \

-s 'config.useKerberosForPasswordAuthentication=["false"]'

Finally, enable the Kerberos execution in the browser authentication flow as ALTERNATIVE or REQUIRED. A few operational tips:

- Keep it ALTERNATIVE so that a failed SPNEGO negotiation falls back gracefully to the standard login form.

- The browser side also needs configuration: Intranet zone settings (Windows GPO) or the negotiate URI allowlist in Firefox.

- Protect the keytab with file permissions 600; in container environments, inject it as a mounted Secret.

- Setting `useKerberosForPasswordAuthentication` to true makes the login form validate passwords via Kerberos instead of an LDAP bind. KDC load and lockout policy behavior change, so false is generally recommended.

Configuring Attribute Mappers

Mappers connect LDAP attributes to Keycloak user attributes and model fields. When the provider is created, vendor-appropriate default mappers (username, email, first name, last name, etc.) are generated automatically, and you add more as needed.

| Mapper type | Purpose | Examples |

| --- | --- | --- |

| user-attribute-ldap-mapper | LDAP attribute to user attribute | mobile, department, employeeNumber |

| full-name-ldap-mapper | Split/join cn or displayName into first/last name | AD displayName |

| hardcoded-attribute-mapper | Inject a fixed value | source=ldap |

| group-ldap-mapper | LDAP groups to Keycloak groups | see section below |

| role-ldap-mapper | LDAP groups/entries to roles | legacy role schemes |

| msad-user-account-control-mapper | AD account state linkage | see section above |

An example that pulls department information into a user attribute:

./kcadm.sh create components -r myrealm \

-s name=department-mapper \

-s providerId=user-attribute-ldap-mapper \

-s providerType=org.keycloak.storage.ldap.mappers.LDAPStorageMapper \

-s parentId=COMPONENT_ID \

-s 'config."user.model.attribute"=["department"]' \

-s 'config."ldap.attribute"=["department"]' \

-s 'config."read.only"=["true"]' \

-s 'config."always.read.value.from.ldap"=["true"]' \

-s 'config."is.mandatory.in.ldap"=["false"]'

Attributes imported this way can be exposed as token claims via protocol mappers in a client scope. That completes the pipeline: "AD department attribute → Keycloak user attribute → JWT claim." Points to watch:

- Setting `always.read.value.from.ldap` to true reads LDAP on every lookup — always fresh, but heavier. For rarely changing attributes, prefer false and rely on sync.

- In READ_ONLY edit mode, also set `read.only=true` on mappers for consistency.

- Binary attributes (objectGUID, photos, etc.) require the `is.binary.attribute` option.

Group and Role Mapping

group-ldap-mapper

Configuration that imports AD security groups into the Keycloak group tree:

./kcadm.sh create components -r myrealm \

-s name=ad-groups \

-s providerId=group-ldap-mapper \

-s providerType=org.keycloak.storage.ldap.mappers.LDAPStorageMapper \

-s parentId=COMPONENT_ID \

-s 'config."groups.dn"=["OU=Groups,DC=corp,DC=example,DC=com"]' \

-s 'config."group.name.ldap.attribute"=["cn"]' \

-s 'config."group.object.classes"=["group"]' \

-s 'config."membership.ldap.attribute"=["member"]' \

-s 'config."membership.attribute.type"=["DN"]' \

-s 'config."membership.user.ldap.attribute"=["sAMAccountName"]' \

-s 'config."mode"=["READ_ONLY"]' \

-s 'config."user.roles.retrieve.strategy"=["LOAD_GROUPS_BY_MEMBER_ATTRIBUTE"]' \

-s 'config."preserve.group.inheritance"=["true"]' \

-s 'config."drop.non.existing.groups.during.sync"=["false"]'

Key options explained:

- `user.roles.retrieve.strategy` controls how memberships are resolved. For AD, `GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE` (using the user's memberOf attribute) is efficient; if you need nested groups expanded, use `LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY` (leveraging AD's matching rule in chain). The recursive strategy issues heavy queries against AD, so performance validation is mandatory in large environments.

- With `preserve.group.inheritance` set to true, the LDAP group hierarchy is reproduced as a Keycloak group tree. If your group structure is flat, keeping it false is simpler.

- `drop.non.existing.groups.during.sync` decides whether groups that disappeared from LDAP are removed during sync. True keeps things tidy, but role mappings and permissions attached to those groups disappear with them — be careful.

Role mapping strategies

After importing groups, there are two patterns for granting authorization.

1. **Assign realm/client roles to groups**: map the client role `app-admin` to the Keycloak group "AD-App-Admins". AD group membership directly becomes application authorization. This is the most common and recommended pattern.

2. **Create roles directly with role-ldap-mapper**: convert LDAP groups straight into Keycloak roles. Suitable for simple cases where you only need roles and no group tree, but mixing groups and roles becomes hard to manage — standardize on one pattern.

When putting group information into tokens, add the group membership protocol mapper to a client scope and agree with the applications on whether to use the full path (slash-delimited hierarchy) to avoid parsing incidents.

Password Policy Conflicts

In WRITABLE mode, the most frequent problem is password policy conflicts. Keycloak realms have a password policy, and AD has its own domain password policy (complexity, minimum length, history).

User → Keycloak password change form

|

| (1) Keycloak realm policy check — passes

v

LDAP modify (unicodePwd)

|

| (2) AD domain policy check — fails!

v

LDAPException: WILL_NOT_PERFORM (error code 53)

If Keycloak's policy passes but AD rejects, the user sees only an opaque error. Practical guidelines:

- **Make the Keycloak realm policy a superset of (stricter than) the AD policy.** For example, if AD requires "minimum 8 characters, complexity on, 24-password history," set Keycloak to "minimum 12 characters, at least one upper/lower/digit/special, 24-password history." When Keycloak rejects first, the user receives a clear policy message.

- If AD enforces a **minimum password age**, an immediate re-change right after a change will be refused. Check that this does not collide with your helpdesk reset flow.

- AD password changes **require LDAPS**. Without an LDAPS 636 connection, unicodePwd modification is refused.

- Error code 53 (WILL_NOT_PERFORM) in the logs usually indicates a policy violation or an insecure channel. Error code 19 (CONSTRAINT_VIOLATION) is often history or minimum-age violations.

Also, if AD account lockout and Keycloak brute force detection both operate, the user experience becomes confusing. The clean division of labor is usually: "delegate lockout to AD, use Keycloak brute force detection as a supplementary alerting signal."

Performance Tuning for Large Directories

Things to consider when federating directories with 100,000+ users.

Pagination and search scope

./kcadm.sh update components/COMPONENT_ID -r myrealm \

-s 'config.pagination=["true"]' \

-s 'config.batchSizeForSync=["1000"]'

- Enabling `pagination` uses the LDAP Simple Paged Results control to receive large result sets in chunks. AD returns at most 1000 entries at a time by default (MaxPageSize), so running a full sync without pagination silently drops every user after the first 1000.

- Constrain `usersDn` to the narrowest OU possible; if you must search SUBTREE from a broad base, narrow the target with a custom LDAP filter.

- Using the AD global catalog (ports 3268/3269) speeds up searches in multi-domain forests, but attributes not replicated to the global catalog cannot be retrieved — confirm that every attribute your mappers need is in the partial attribute set.

Connection pool and timeouts

./kcadm.sh update components/COMPONENT_ID -r myrealm \

-s 'config.connectionPooling=["true"]' \

-s 'config.connectionTimeout=["5000"]' \

-s 'config.readTimeout=["10000"]'

- Always enable connection pooling. A fresh TCP+TLS handshake per bind adds hundreds of milliseconds to every login.

- Without timeouts, a directory outage leaves Keycloak worker threads waiting forever and paralyzes all logins. Connection 5 s and read 10 s are reasonable starting points.

Cache policies

User Storage providers support cache policies.

| Policy | Behavior | Best for |

| --- | --- | --- |

| DEFAULT | Use the standard user cache | Most cases |

| EVICT_DAILY | Invalidate at a fixed time daily | Directories refreshed by nightly batches |

| EVICT_WEEKLY | Invalidate weekly at a fixed day/time | Rarely changing environments |

| MAX_LIFESPAN | Invalidate after a duration (ms) | Clear freshness requirements |

| NO_CACHE | No caching | Debugging, extreme freshness needs |

Caching reduces LDAP lookups, but it is also the cause of "we disabled the account in AD but they can still log into Keycloak." In security-sensitive environments, set MAX_LIFESPAN short (e.g., 5 minutes) and tune while monitoring LDAP load. The cache can also be invalidated manually via the Admin REST API clear-user-cache endpoint.

Troubleshooting Sync Failures

Common failure patterns seen in production and how to respond.

First step in diagnosis — log levels

Add debug logging for the LDAP category to the Keycloak start options

bin/kc.sh start \

--log-level=INFO,org.keycloak.storage.ldap:DEBUG \

--spi-connections-http-client-default-connection-pool-size=128

Checklist by symptom

| Symptom | Likely cause | How to verify |

| --- | --- | --- |

| Intermittent login failures | Connection pool exhaustion, one DC down | Per-LDAP-server response times, pool settings |

| Full sync stops at 1000 users | Pagination disabled | pagination setting, AD MaxPageSize |

| Duplicate users created | uuidLDAPAttribute changed/misconfigured | Verify objectGUID mapping |

| Leavers can still log in | Only changed sync runs, cache remains | Full sync period, cache policy |

| Password change fails (code 53) | Plaintext channel, AD policy violation | LDAPS in use, domain policy |

| Sync never finishes | Full scan of a huge OU, unindexed filter | Custom filter, AD indexed attributes |

| TLS handshake failure | Corporate CA missing from truststore | useTruststoreSpi, truststore contents |

Reproducing outside Keycloak with ldapsearch

The fastest way to determine whether the problem is Keycloak configuration or the directory itself is an ldapsearch with identical conditions.

Manually reproduce the same search Keycloak performs

ldapsearch -H ldaps://ad01.corp.example.com:636 \

-D "CN=svc-keycloak,OU=ServiceAccounts,DC=corp,DC=example,DC=com" \

-W \

-b "OU=Employees,DC=corp,DC=example,DC=com" \

-s sub \

-E pr=1000/noprompt \

"(&(objectCategory=person)(sAMAccountName=jdoe))" \

sAMAccountName mail userAccountControl whenChanged

If this command returns quickly, the problem is on the Keycloak side; if it is already slow here, the problem is in the directory or the network. Filtering on unindexed AD attributes causes full scans, so check with your AD administrators that the attributes used in filters (sAMAccountName, mail, etc.) are indexed.

High availability

You can specify multiple servers in connectionUrl, separated by spaces.

ldaps://ad01.corp.example.com:636 ldaps://ad02.corp.example.com:636

This is only simple failover, though. In practice it is more robust to place the directory behind DNS round-robin or an LDAP proxy (e.g., the DC locator of the domain DNS) and handle health checking at the infrastructure layer.

Hybrid Scenario — AD Employees + Social External Users

Real-world services often have the mixed requirement of "employees via AD, external partners or customers via social/email signup." In Keycloak you can combine the following within a single realm.

+---------------------------+

| Realm: company |

| |

Employees --------->| User Federation (LDAP/AD) |

(corp laptop, | |

Kerberos SSO) | Identity Providers |

Partners ---------->| - Google / GitHub |

Customers --------->| - Apple / Kakao |

| |

| Local users (self-reg) |

+---------------------------+

Design points:

- **Account conflict handling**: if an employee tries to log in with Google using their corporate email, it collides with the federated user that has the same email. You must explicitly design how the account linking step in the First Broker Login flow behaves — disallow automatic linking and require authentication with the existing account. Allowing automatic linking opens an account-takeover path through IdPs that do not verify email ownership.

- **Consider separate realms**: if the security requirements of employees and customers differ significantly (MFA policy, session lifetimes, password policy), separating realms is operationally simpler. To serve both realms from a single app, the app must handle multiple issuers, or you place a broker realm in between.

- **Attribute-based distinction**: if you stay with a single realm, stamp federated users with an attribute like `source=ldap` via a hardcoded attribute mapper and expose it as a token claim so applications can tell where a user came from.

- From a 2026-trend perspective, gradually rolling out passkeys to employees (the conditional UI integrated into the login form in Keycloak 26.6) while reducing dependence on AD passwords is an effective strategy.

Migration Strategy — from LDAP to Keycloak Built-in Storage

LDAP federation is often a transition, not a destination. Here is a migration scenario for cutting the directory dependency and making Keycloak (and the database behind it) the source of truth.

Phased strategy

Phase 1 Phase 2 Phase 3 Phase 4

READ_ONLY → switch to UNSYNCED → progressively → remove LDAP

federation (writes become capture credentials (detach the

(status quo) independent) (store local hash federation link)

at login)

1. **Phase 1 — stabilize with READ_ONLY**: with import mode plus full/changed sync, ensure every user exists as a federated entry in the Keycloak local DB. Consolidate all application authentication onto Keycloak.

2. **Phase 2 — switch to UNSYNCED**: profile changes start landing only in the local store. From this point, switch user lifecycle events (HR integration, etc.) to flow directly through the Keycloak Admin API (or a SCIM bridge).

3. **Phase 3 — capture credentials**: the trickiest part. LDAP password hashes usually cannot be extracted (impossible with AD), so let Keycloak store a local hash (argon2 by default) of passwords that validate successfully at login. Leverage the UNSYNCED behavior where password changes are stored locally, and/or run a campaign after a set period requiring all users to reset passwords or register passkeys. Folding a "passkey enrollment campaign" into this phase achieves migration and passwordless transition in one move.

4. **Phase 4 — detach the link**: once all users (or a threshold share) have local credentials, remove the LDAP provider. Removing the provider severs the federated links, but the imported user entries remain. Always rehearse the provider removal in a staging realm beforehand and verify user state (credential presence, required actions).

Migration checklist

- Identify **dormant users** from login statistics and either exclude them from migration or define a separate reactivation procedure.

- If group/role mappings depend on LDAP mappers, replicate them as native Keycloak groups and move the permission mappings before migrating.

- If applications consume LDAP-derived attributes (employeeNumber, etc.) as claims, secure a new supply path for those attributes (HR API, etc.) first.

- Rollback plan: during Phases 2–3, LDAP and local data diverge, so document the criteria for which data is discarded on rollback.

- Keycloak 26.x Organizations and Workflows can be used to build post-migration user lifecycle automation (onboarding/offboarding) inside Keycloak.

Summary of Operational Best Practices

- Default to LDAPS with truststore validation; manage the bind account with least privilege and a secrets manager.

- Make the edit mode express intent: READ_ONLY if AD is the source of truth, UNSYNCED if you are mid-migration.

- Start with weekly full sync plus hourly changed sync, and always verify propagation of deletions/deactivations.

- Pagination, connection pooling, and timeouts are mandatory settings.

- Make the Keycloak password policy the stricter one so users see clear error messages.

- Define the division of labor between AD lockout policy and Keycloak brute force detection.

- Chaos-test directory failure scenarios (timeouts, fallback, cache lifetimes).

- Migrate in stages — READ_ONLY → UNSYNCED → credential capture → link detachment — and document rollback criteria for each stage.

Closing

User Federation is among the "oldest" features of Keycloak, yet in enterprise practice it still decides whether an adoption succeeds. Once you precisely understand the two axes of edit mode and sync strategy, most design decisions become obvious; and if you take care of the three operational settings — pagination, cache, timeouts — it runs reliably even at large scale.

In the long run, treat LDAP federation as a transition rather than a permanent state, and draw a roadmap toward passkeys and modern user lifecycle management (SCIM, Workflows) alongside it. In the next article we will cover fine-grained authorization with Keycloak Authorization Services.

References

- [Keycloak Server Administration Guide — User Federation](https://www.keycloak.org/docs/latest/server_admin/index.html#_user-storage-federation)

- [Keycloak Documentation](https://www.keycloak.org/documentation)

- [Keycloak 26.6.0 Release Notes](https://www.keycloak.org/docs/latest/release_notes/index.html)

- [Keycloak User Storage SPI — Server Developer Guide](https://www.keycloak.org/docs/latest/server_development/index.html#_user-storage-spi)

- [RFC 4511 — LDAP: The Protocol](https://datatracker.ietf.org/doc/html/rfc4511)

- [RFC 2696 — LDAP Control Extension for Simple Paged Results](https://datatracker.ietf.org/doc/html/rfc2696)

- [RFC 4178 — SPNEGO: The Simple and Protected GSS-API Negotiation Mechanism](https://datatracker.ietf.org/doc/html/rfc4178)

- [Microsoft Learn — UserAccountControl property flags](https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/useraccountcontrol-manipulate-account-properties)

- [Microsoft Learn — Active Directory Schema](https://learn.microsoft.com/en-us/windows/win32/adschema/active-directory-schema)

- [RFC 7644 — SCIM Protocol](https://datatracker.ietf.org/doc/html/rfc7644)

- [W3C WebAuthn Level 3](https://www.w3.org/TR/webauthn-3/)

- [FIDO Alliance — Passkeys](https://fidoalliance.org/passkeys/)

현재 단락 (1/304)

When adopting Keycloak in an enterprise environment, the very first challenge you run into is: "What...

작성 글자: 0원문 글자: 26,216작성 단락: 0/304