- Published on
Keycloak LDAP/Active Directory 連携 — User Federation 実践ガイド
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- User Federation アーキテクチャの理解
- LDAP Provider 設定の詳細
- Active Directory 固有の設定
- Attribute Mapper の構成
- グループと Role のマッピング
- パスワードポリシー衝突の問題
- 大規模ディレクトリのパフォーマンスチューニング
- 同期障害のトラブルシューティング
- ハイブリッドシナリオ — AD 社員 + ソーシャル外部ユーザー
- 移行戦略 — LDAP から Keycloak 組み込みストレージへ
- 運用ベストプラクティスまとめ
- おわりに
- 参考資料
はじめに
エンタープライズ環境で Keycloak を導入する際、最初にぶつかる課題は「既に存在するユーザーディレクトリをどうするか」です。ほとんどの組織は、数年から数十年運用してきた Active Directory(AD)や OpenLDAP のようなディレクトリサービスを持っており、数千から数十万のユーザーアカウント、グループ、パスワードポリシーがその中に生きています。
2026 年現在、Keycloak は 26.6.x 系(2026 年 5 月時点で 26.6.2)まで進化し、ログインフォームへの passkeys 統合、FAPI 2.0 Security Profile Final 対応、Workflows による realm 管理の自動化といったモダンな機能を備えていますが、エンタープライズ導入の成否は依然としてレガシーディレクトリとの連携品質で決まります。Zero Trust と identity-first セキュリティが当たり前になった時代でも、その出発点は「信頼できる単一のユーザーストア」だからです。
本記事では、Keycloak User Federation のアーキテクチャから LDAP provider の詳細設定、AD 固有のオプション、attribute/group マッピング、大規模ディレクトリのパフォーマンスチューニング、同期障害のトラブルシューティング、そして長期的に LDAP を撤去して Keycloak 組み込みストレージへ移行する戦略まで、実務の視点で扱います。
User Federation アーキテクチャの理解
基本構造
Keycloak の User Federation は、外部ユーザーストアを Keycloak のユーザーモデルに「接続」するレイヤーです。重要なのは、Keycloak が外部ストアを置き換えるのではなく、User Storage SPI を通じて委譲(delegation)とキャッシングを行うという点です。
+-------------------+ +----------------------------+
| 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) |
+--------------+
動作の流れを整理すると次の通りです。
- ユーザーがログインを試みると、Keycloak はまず内部キャッシュとローカル DB でユーザーを探します。
- 見つからなければ、登録された User Storage provider(LDAP provider)に優先順位の順で問い合わせます。
- LDAP でユーザーが見つかると、Keycloak はローカル DB に「federated user」エントリを作成します。このエントリは LDAP エントリへのリンク(元の DN、provider ID)を保持します。
- パスワード検証は、設定に応じて LDAP bind に委譲されるか、Keycloak 内部で処理されます。
ここで重要なのは、federated user は LDAP のミラーではなくリンクであるという点です。どの属性をローカルにコピーするか、書き込み操作をどこへ送るかは、すべて edit mode と mapper の設定が決定します。
import モードと no-import モード
LDAP provider 設定の importEnabled オプションは、運用特性を大きく変えます。
| モード | 動作 | 利点 | 欠点 |
|---|---|---|---|
| import on(デフォルト) | ユーザーをローカル DB にコピーしリンクを保持 | 検索・参照が高速、オフライントークンが安定 | 同期が必要、DB 容量が増加 |
| no-import | リクエストごとに LDAP を直接参照 | 同期不要、常に最新 | LDAP 負荷増加、一部機能に制約 |
大規模組織(5 万人以上)では import モード + 定期同期の組み合わせが一般的です。no-import は、ディレクトリが高速で近くにあり、ユーザー数が少なく、「LDAP が常に真実の源泉」であるべき場合に適しています。
LDAP Provider 設定の詳細
基本接続設定
Admin Console の User Federation メニューから設定することもできますが、再現可能なインフラのために kcadm.sh CLI や Terraform(keycloak provider)でコード化することを推奨します。
# 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"]'
接続に関する重要なポイントは次の通りです。
- 必ず LDAPS(636)または StartTLS を使用します。平文 LDAP(389)で bind パスワードがネットワークを流れる構成は、監査で即座に指摘される対象です。
bindDnに使うサービスアカウントは最小権限(読み取り専用、必要な OU に限定)で作成し、パスワードは Vault のようなシークレットマネージャーから注入します。- AD の
uuidLDAPAttributeはobjectGUID、OpenLDAP はentryUUIDを使います。この値が federated link の不変キーになるため、誤設定はユーザー重複作成の事故につながります。
Edit Mode — READ_ONLY、WRITABLE、UNSYNCED
edit mode は「書き込み操作がどこへ行くか」を決める最も重要な設定です。
| Edit Mode | プロフィール変更 | パスワード変更 | 適したシナリオ |
|---|---|---|---|
| READ_ONLY | 不可(例外発生) | 不可 | AD が唯一の真実の源泉、HR システムが AD を管理 |
| WRITABLE | LDAP に直接書き込み | LDAP に書き込み | Keycloak をセルフサービスポータルとして使用 |
| UNSYNCED | Keycloak ローカル DB のみに書き込み | ローカルのみに保存 | LDAP は読み取りのみ、段階的に独立 |
各モードの運用上の含意を見ていきます。
READ_ONLY は最も安全なデフォルトです。ユーザーが Keycloak Account Console でプロフィールを編集しようとするとエラーが発生するため、Account Console の該当機能を無効化するか required action を調整する必要があります。パスワード変更リクエストは拒否されるため、「パスワード変更は社内ポータルで行ってください」といった案内が必要です。
WRITABLE では Keycloak が LDAP の書き込みクライアントになります。bind サービスアカウントに書き込み権限が必要になり、AD の場合はパスワード変更のために LDAPS が必須です(AD は非セキュアなチャネルでの unicodePwd 変更を拒否します)。また、Keycloak の required action(例:初回ログイン時のパスワード変更)が実際の AD パスワードを変更することになるため、AD 側のパスワードポリシーとの衝突を必ず検討してください。
UNSYNCED は興味深い中間地帯です。LDAP からユーザーを読み込むものの、以降の変更は Keycloak ローカルにのみ保存されます。事実上「LDAP をシードデータとして使う段階的移行モード」であり、後述する移行戦略の中核ツールです。ただしこのモードでは LDAP と Keycloak のデータが時間とともに乖離(diverge)するため、どちらが真実かという運用ポリシーを明確にしなければなりません。
同期戦略 — Full Sync と Changed Users Sync
インポート(import)モードでは、LDAP の変更を Keycloak ローカル DB に反映する同期が必要です。
# フル同期のトリガー(component ID は作成時に返された値)
./kcadm.sh create user-storage/COMPONENT_ID/sync?action=triggerFullSync -r myrealm
# 差分同期のトリガー
./kcadm.sh create user-storage/COMPONENT_ID/sync?action=triggerChangedUsersSync -r myrealm
周期設定は provider config で行います。
./kcadm.sh update components/COMPONENT_ID -r myrealm \
-s 'config.fullSyncPeriod=["604800"]' \
-s 'config.changedSyncPeriod=["3600"]'
| 戦略 | 推奨周期 | 動作 | 注意点 |
|---|---|---|---|
| Full sync | 週 1 回(604800 秒) | 全ユーザーの再取得と更新 | 大規模ディレクトリでは数十分かかることも |
| Changed users sync | 1 時間(3600 秒) | 更新タイムスタンプベースの増分取得 | 削除を検知できない |
changed sync は LDAP の modifyTimestamp 属性(AD は whenChanged)を基準に、変更されたエントリのみを取得します。ここでよく見落とされる罠が二つあります。
- 削除は検知できません。 LDAP で削除されたユーザーは changed sync では消えません。full sync 時に「存在しないユーザーを削除する」動作が適用されるか、別途クリーンアップジョブが必要です。退職者アカウントが Keycloak に残存するのはセキュリティ事故の定番原因なので、full sync 周期とあわせて無効化ポリシー(AD でアカウント無効化時に Keycloak でも無効化)を必ず設計してください。
- タイムスタンプはディレクトリサーバー基準です。Keycloak サーバーとディレクトリサーバーの時計がずれると変更を取りこぼす可能性があります。NTP 同期を確認しましょう。
Active Directory 固有の設定
MSAD User Account Control Mapper
AD は標準 LDAP と異なり、アカウント状態を userAccountControl というビットフラグ属性で管理します。Keycloak の MSAD user account control mapper はこのフラグを解釈し、Keycloak のユーザー状態と連動させます。
userAccountControl の主要ビットフラグ
---------------------------------------------
0x0002 ACCOUNTDISABLE アカウント無効化
0x0010 LOCKOUT アカウントロック
0x0020 PASSWD_NOTREQD パスワード不要
0x10000 DONT_EXPIRE_PASSWD パスワード無期限
0x800000 PASSWORD_EXPIRED パスワード期限切れ
例) 512 (0x200) = 通常アカウント
514 (0x202) = 無効化された通常アカウント
66048 = 通常 + パスワード無期限
この mapper を有効にすると、次のことが可能になります。
- AD で無効化されたアカウント(ACCOUNTDISABLE)は Keycloak でもログイン不可になります。
- AD の PASSWORD_EXPIRED 状態が検知されると、Keycloak が UPDATE_PASSWORD required action をトリガーします(WRITABLE モード時)。
pwdLastSetが 0 のユーザー(次回ログオン時にパスワード変更が必要)もパスワード更新フローへ誘導されます。
もう一点、AD 連携では userObjectClasses に computer が混入しないよう検索フィルターを調整するのが良いでしょう。AD の user objectClass はコンピューターアカウントにも適用されるためです。
追加のユーザー LDAP フィルター例 (custom user LDAP filter)
(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
このフィルターは person カテゴリかつ無効化ビットが立っていないアカウントだけを取得します。1.2.840.113556.1.4.803 は AD のビット AND マッチングルールの OID です。
Kerberos / SPNEGO 統合
社内 Windows PC にドメインログインしたユーザーが、ブラウザから Keycloak にアクセスする際にパスワード入力なしで SSO できるようにするには、Kerberos/SPNEGO 統合を構成します。
まず AD で Keycloak 用サービスアカウントに SPN(Service Principal Name)を登録し、keytab を発行します。
# AD ドメインコントローラー上で(管理者権限)
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
Keycloak サーバー側には krb5.conf を配置し、LDAP provider の Kerberos 統合オプションを有効にします。
# /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"]'
最後にブラウザ認証フローで Kerberos execution を ALTERNATIVE または REQUIRED として有効にします。運用上のヒントをいくつか付け加えます。
- SPNEGO ネゴシエーションが失敗したとき標準ログインフォームへ自然にフォールバックできるよう、ALTERNATIVE にしておくのが安全です。
- ブラウザ側でも Intranet zone 設定(Windows GPO)や Firefox の negotiate URI 許可リスト設定が必要です。
- keytab はファイル権限 600 で保護し、コンテナ環境では Secret マウントで注入します。
useKerberosForPasswordAuthenticationを true にすると、ログインフォームのパスワード検証も LDAP bind の代わりに Kerberos で行われます。KDC の負荷とロックアウトポリシーの挙動が変わるため、一般的には false を推奨します。
Attribute Mapper の構成
LDAP 属性と Keycloak のユーザー属性・モデルを結びつけるのが mapper です。provider 作成時に vendor に応じたデフォルト mapper(username、email、first name、last name など)が自動生成され、必要に応じて追加します。
| Mapper 種類 | 用途 | 例 |
|---|---|---|
| user-attribute-ldap-mapper | LDAP 属性をユーザー属性へ | mobile、department、employeeNumber |
| full-name-ldap-mapper | cn や displayName を姓・名に分解/結合 | AD displayName |
| hardcoded-attribute-mapper | 固定値の注入 | source=ldap |
| group-ldap-mapper | LDAP グループを Keycloak グループへ | 下記の節を参照 |
| role-ldap-mapper | LDAP グループ/エントリを role へ | レガシー role 体系 |
| msad-user-account-control-mapper | AD アカウント状態の連動 | 上記の節を参照 |
部署情報をユーザー属性として取り込む例です。
./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"]'
こうして取り込んだ属性は、client scope の protocol mapper を通じてトークンクレームとして公開できます。つまり「AD の department 属性 → Keycloak user attribute → JWT claim」というパイプラインが完成します。注意点は次の通りです。
always.read.value.from.ldapを true にすると参照のたびに LDAP を読むため常に最新ですが、負荷が増えます。変更が頻繁でない属性は false にして同期に任せる方が良いでしょう。- READ_ONLY edit mode では mapper も
read.only=trueに揃えることで一貫性が保たれます。 - バイナリ属性(objectGUID、写真など)は
is.binary.attributeオプションを有効にする必要があります。
グループと Role のマッピング
group-ldap-mapper
AD セキュリティグループを Keycloak グループツリーに取り込む設定です。
./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"]'
主要オプションを解説します。
user.roles.retrieve.strategyはメンバーシップの解決方法です。AD ならGET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE(ユーザーの memberOf 属性を活用)が効率的で、ネストグループまで展開するにはLOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY(AD の matching rule in chain を活用)を使います。ただし recursive 戦略は AD に重いクエリを投げるため、大規模環境ではパフォーマンス検証が必須です。preserve.group.inheritanceを true にすると、LDAP のグループ階層が Keycloak グループツリーとして再現されます。フラットなグループ構造なら false の方がシンプルです。drop.non.existing.groups.during.syncは、LDAP から消えたグループを同期時に削除するか決めます。true にすればきれいに保てますが、そのグループに紐づけた role マッピングや権限も一緒に消えるため慎重に。
role マッピング戦略
グループを取り込んだ後の権限付与には二つのパターンがあります。
- グループに realm/client role を付与:Keycloak グループ「AD-App-Admins」に client role
app-adminをマッピング。AD グループメンバーシップがそのままアプリケーション権限になります。最も一般的で推奨されるパターンです。 - role-ldap-mapper で直接 role を生成:LDAP グループを Keycloak role に直接変換。グループツリーが不要で role だけ欲しいシンプルなケースに適しますが、グループと role が混在すると管理が困難になるため、どちらか一方のパターンに統一することを推奨します。
トークンにグループ情報を入れる際は、client scope に group membership protocol mapper を追加し、full path の使用有無(スラッシュ区切りの階層パス)をアプリケーション側と合意しておくことで、パース事故を防げます。
パスワードポリシー衝突の問題
WRITABLE モードで最も頻繁に発生する問題が、パスワードポリシーの衝突です。Keycloak realm にも password policy があり、AD にもドメインパスワードポリシー(複雑さ、最小長、履歴)があるためです。
ユーザー → Keycloak パスワード変更フォーム
|
| (1) Keycloak realm policy チェック — 通過
v
LDAP modify (unicodePwd)
|
| (2) AD ドメインポリシーチェック — 失敗!
v
LDAPException: WILL_NOT_PERFORM (error code 53)
Keycloak のポリシーは通過したのに AD が拒否すると、ユーザーには曖昧なエラーしか表示されません。実務上のガイドラインは次の通りです。
- Keycloak realm policy を AD ポリシーの上位集合(より厳しく)に設定します。例えば AD が「最小 8 文字、複雑さ有効、履歴 24 個」なら、Keycloak は「最小 12 文字、大文字・小文字・数字・記号それぞれ 1 つ以上、履歴 24 個」に合わせます。Keycloak 側で先に弾かれれば、ユーザーは明確なポリシー案内メッセージを受け取れます。
- AD の**パスワード最小使用期間(minimum password age)**ポリシーがあると、変更直後の再変更は拒否されます。ヘルプデスクのリセットフローと衝突しないか確認してください。
- AD のパスワード変更は LDAPS 必須です。LDAPS 636 接続でなければ unicodePwd の変更は拒否されます。
- ログに error code 53(WILL_NOT_PERFORM)が見えたら、大半はポリシー違反か非セキュアチャネルの問題です。error code 19(CONSTRAINT_VIOLATION)は履歴・最小期間違反であることが多いです。
また、AD のアカウントロックアウトポリシーと Keycloak の brute force detection が二重に動作すると、ユーザー体験が混乱します。一般的には「ロックアウトは AD に委譲し、Keycloak の brute force は補助的なアラート用途」と役割分担するのがきれいです。
大規模ディレクトリのパフォーマンスチューニング
ユーザー 10 万人以上のディレクトリを連携する際に考慮すべき項目です。
ページネーションと検索範囲
./kcadm.sh update components/COMPONENT_ID -r myrealm \
-s 'config.pagination=["true"]' \
-s 'config.batchSizeForSync=["1000"]'
paginationを有効にすると、LDAP Simple Paged Results コントロールで大量の結果を分割受信します。AD はデフォルトで一度に 1000 件(MaxPageSize)までしか返さないため、ページネーションなしで full sync を回すと 1000 人以降のユーザーが欠落する事故が起きます。usersDnは可能な限り狭い OU に限定し、広いベースから SUBTREE 検索が必要なら custom LDAP filter で対象を絞ります。- AD グローバルカタログ(3268/3269 ポート)を使うとマルチドメインフォレストでの検索が速くなりますが、グローバルカタログに複製されない属性は取得できないため、mapper 対象の属性が partial attribute set に含まれるか確認が必要です。
コネクションプールとタイムアウト
./kcadm.sh update components/COMPONENT_ID -r myrealm \
-s 'config.connectionPooling=["true"]' \
-s 'config.connectionTimeout=["5000"]' \
-s 'config.readTimeout=["10000"]'
- コネクションプーリングは必ず有効にします。bind のたびに TCP+TLS ハンドシェイクをやり直すと、ログイン遅延が数百 ms 単位で増えます。
- タイムアウトを設定しないと、ディレクトリ障害時に Keycloak のワーカースレッドが無限待機し、ログイン全体が麻痺します。connection 5 秒、read 10 秒あたりが妥当な出発点です。
キャッシュポリシー
User Storage provider にはキャッシュポリシーを設定できます。
| ポリシー | 動作 | 適したケース |
|---|---|---|
| DEFAULT | 標準の user cache を使用 | ほとんどの場合 |
| EVICT_DAILY | 毎日指定時刻にキャッシュ無効化 | 夜間バッチでディレクトリが更新される環境 |
| EVICT_WEEKLY | 毎週指定曜日・時刻に無効化 | 変更がまれな環境 |
| MAX_LIFESPAN | 指定時間(ms)後に無効化 | 鮮度要件が明確な環境 |
| NO_CACHE | キャッシュ不使用 | デバッグ、極端な鮮度要件 |
キャッシュのおかげで LDAP 参照は減りますが、「AD で無効化したのに Keycloak ではまだログインできる」という状況の原因にもなります。セキュリティ要件が厳しい環境では MAX_LIFESPAN を短め(例:5 分)に設定し、LDAP 負荷をモニタリングしながら調整してください。キャッシュは Admin REST API の clear-user-cache エンドポイントで手動無効化も可能です。
同期障害のトラブルシューティング
運用中に遭遇する代表的な障害パターンと対応を整理します。
診断の第一歩 — ログレベル
# Keycloak 起動オプションに LDAP カテゴリのデバッグログを追加
bin/kc.sh start \
--log-level=INFO,org.keycloak.storage.ldap:DEBUG \
--spi-connections-http-client-default-connection-pool-size=128
症状別チェックリスト
| 症状 | 有力な原因 | 確認方法 |
|---|---|---|
| ログインが間欠的に失敗 | コネクションプール枯渇、DC 1 台の障害 | LDAP サーバー別の応答時間、プール設定 |
| full sync で 1000 人しか同期されない | ページネーション無効 | pagination 設定、AD MaxPageSize |
| ユーザーの重複作成 | uuidLDAPAttribute の変更・誤設定 | objectGUID マッピングの確認 |
| 退職者がログイン可能 | changed sync のみ動作、キャッシュ残存 | full sync 周期、キャッシュポリシー |
| パスワード変更失敗 (code 53) | 平文チャネル、AD ポリシー違反 | LDAPS の使用有無、ドメインポリシー |
| 同期が終わらない | 巨大 OU の全スキャン、未インデックスのフィルター | custom filter、AD インデックス属性 |
| TLS ハンドシェイク失敗 | truststore に社内 CA 未登録 | useTruststoreSpi、truststore の内容 |
ldapsearch で Keycloak の外から再現する
問題が Keycloak の設定なのかディレクトリ自体なのかを切り分ける最速の方法は、同一条件の ldapsearch です。
# Keycloak が実行するのと同じ検索を手動で再現
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
このコマンドが素早く結果を返すなら問題は Keycloak 側の設定で、この時点で遅ければディレクトリ・ネットワークの問題です。AD の未インデックス属性でフィルタリングすると全スキャンが発生するため、フィルターに使う属性(sAMAccountName、mail など)がインデックスされているか AD 管理者と確認してください。
高可用性構成
connectionUrl にはスペース区切りで複数サーバーを指定できます。
ldaps://ad01.corp.example.com:636 ldaps://ad02.corp.example.com:636
ただしこれは単純なフェイルオーバーなので、実務では DNS ラウンドロビンや LDAP プロキシ(例:ドメイン DNS の DC ロケーター)の背後に置き、ヘルスチェックをインフラ層で処理する方が堅牢です。
ハイブリッドシナリオ — AD 社員 + ソーシャル外部ユーザー
現実のサービスでは「社員は AD で、外部パートナーや顧客はソーシャル・メール登録で」という混合要件が多くあります。Keycloak では一つの realm の中で次を組み合わせられます。
+---------------------------+
| Realm: company |
| |
Employees --------->| User Federation (LDAP/AD) |
(corp laptop, | |
Kerberos SSO) | Identity Providers |
Partners ---------->| - Google / GitHub |
Customers --------->| - Apple / Kakao |
| |
| Local users (self-reg) |
+---------------------------+
設計のポイントは次の通りです。
- アカウント衝突の処理:社員が会社メールで Google ログインを試みると、同じメールの federated user と衝突します。First Broker Login フローのアカウント連携(link)ステップをどう扱うか — 自動連携を禁止し既存アカウントでの認証を要求する — を明示的に設計する必要があります。自動連携を許可すると、メール所有検証のない IdP を経由したアカウント乗っ取りの経路が生まれます。
- realm 分離の検討:社員と顧客のセキュリティ要件(MFA ポリシー、セッション寿命、パスワードポリシー)が大きく異なる場合、realm を分離する方が運用上シンプルです。単一アプリで二つの realm を受けるには、アプリが multi-issuer を処理するか、間に broker realm を置きます。
- 属性による区別:単一 realm で進めるなら、hardcoded attribute mapper で federated user に
source=ldapのような属性を付与し、トークンクレームとして公開してアプリケーションがユーザーの出自を区別できるようにします。 - 2026 年のトレンドの観点では、社員側に passkeys(Keycloak 26.6 のログインフォーム統合 conditional UI)を段階的に導入し、AD パスワードへの依存度を下げる戦略が有効です。
移行戦略 — LDAP から Keycloak 組み込みストレージへ
LDAP 連携は終着点ではなく過渡期であることが多いです。ディレクトリ依存を断ち、Keycloak(とその背後の DB)を真実の源泉にする移行シナリオを整理します。
段階的戦略
Phase 1 Phase 2 Phase 3 Phase 4
READ_ONLY 連携 → UNSYNCED へ切替 → クレデンシャルの → LDAP 撤去
(現状維持) (書き込み独立) 段階的取得 (federation
(ログイン時に link 解除)
ローカルハッシュ保存)
- Phase 1 — READ_ONLY で安定化:import モード + full/changed sync で、全ユーザーが Keycloak ローカル DB に federated entry として存在する状態を作ります。アプリケーションの認証をすべて Keycloak に集約します。
- Phase 2 — UNSYNCED へ切替:プロフィール変更がローカルにのみ記録されるようになります。この時点から HR 連携などユーザーライフサイクルイベントを Keycloak Admin API(または SCIM ブリッジ)で直接受け取るように移行します。
- Phase 3 — クレデンシャルの取得:最も厄介な部分です。LDAP のパスワードハッシュは通常抽出できないため(AD では不可能)、ログイン時に検証に成功したパスワードを Keycloak がローカルハッシュ(デフォルト argon2)として保存するようにします。UNSYNCED モードでパスワード変更がローカルに保存される動作を活用するか、一定期間後に全ユーザーへパスワードリセット・passkey 登録を要求する方式を併用します。「passkey 登録キャンペーン」をこの段階に組み込めば、移行と passwordless 化を一度に達成できます。
- Phase 4 — リンク解除:すべての(または閾値以上の)ユーザーがローカルクレデンシャルを持ったら、LDAP provider を削除します。provider を削除すると federated link は切れますが、import されたユーザーエントリは残ります。事前にステージング realm で provider 削除後のユーザー状態(クレデンシャル保有有無、required action)を必ずリハーサルしてください。
移行チェックリスト
- ログイン統計で休眠ユーザーを特定し、移行対象から除外するか、別途再活性化手順を用意します。
- グループ・role マッピングが LDAP mapper に依存している場合、移行前に Keycloak ネイティブグループとして複製し、権限マッピングを移します。
- アプリケーションが LDAP 由来の属性(employeeNumber など)をクレームとして使っている場合、その属性の新しい供給経路(HR API など)を先に確保します。
- ロールバック計画:Phase 2〜3 の間は LDAP とローカルデータが乖離するため、ロールバック時にどのデータを捨てるかの基準を文書化します。
- Keycloak 26.x の Organizations 機能と Workflows を活用すれば、移行後のユーザーライフサイクル自動化(オンボーディング・オフボーディング)を Keycloak 内で構成できます。
運用ベストプラクティスまとめ
- LDAPS + truststore 検証を基本とし、bind アカウントは最小権限 + シークレットマネージャー管理。
- edit mode は意図を明確に:真実の源泉が AD なら READ_ONLY、移行中なら UNSYNCED。
- full sync 週 1 回 + changed sync 1 時間を出発点に、削除・無効化の伝播を必ず検証。
- ページネーション有効化、コネクションプール・タイムアウト設定は必須。
- パスワードポリシーは Keycloak 側をより厳しく設定し、ユーザーに明確なエラーを見せること。
- AD のロックアウトポリシーと Keycloak の brute force detection の役割分担を定義すること。
- ディレクトリ障害シナリオ(タイムアウト、フォールバック、キャッシュ寿命)をカオステストで検証すること。
- 移行は READ_ONLY → UNSYNCED → クレデンシャル取得 → リンク解除の段階で進め、各段階でロールバック基準を文書化すること。
おわりに
User Federation は Keycloak の機能の中でも最も「古い」部類に属しますが、エンタープライズの現場では今なお導入成否を分ける核心です。edit mode と同期戦略という二つの軸を正確に理解すればほとんどの設計判断は明確になり、ページネーション・キャッシュ・タイムアウトという三つの運用設定を押さえれば大規模環境でも安定して稼働します。
長期的には LDAP 連携を恒久的な状態ではなく過渡期と捉え、passkeys やモダンなユーザーライフサイクル管理(SCIM、Workflows)へ進むロードマップをあわせて描くことをお勧めします。次回は Keycloak Authorization Services を活用した細粒度の権限制御を取り上げます。
参考資料
- Keycloak Server Administration Guide — User Federation
- Keycloak Documentation
- Keycloak 26.6.0 Release Notes
- Keycloak User Storage SPI — Server Developer Guide
- RFC 4511 — LDAP: The Protocol
- RFC 2696 — LDAP Control Extension for Simple Paged Results
- RFC 4178 — SPNEGO: The Simple and Protected GSS-API Negotiation Mechanism
- Microsoft Learn — UserAccountControl property flags
- Microsoft Learn — Active Directory Schema
- RFC 7644 — SCIM Protocol
- W3C WebAuthn Level 3
- FIDO Alliance — Passkeys