
Keycloak(キークローク)とは、OpenID Connect(OIDC)やSAMLでシングルサインオン(SSO)を提供する、オープンソースのID・アクセス管理サーバーです。ユーザーとグループを自前で管理でき、社内アプリはKeycloakにログインを任せるだけで、パスワード管理やMFAを1か所にまとめられます。
ここではKeycloak 26.7.4(執筆時点の最新版)をPostgreSQLと組み合わせて本番モードで起動し、レルム・ユーザー・グループ・OIDCクライアントを kcadm.sh で作成したうえで、ログイン機能を持たない社内アプリをoauth2-proxy経由でSSO化するところまでを、実際にブラウザーでログインして確かめた手順として示します。途中で実際に出たエラーは、原因と直し方をそのまま載せています。
この記事でわかること
- Keycloakの位置づけ(Apache License 2.0、レルム・クライアント・グループの関係)とEntra IDとの使い分け
- 本番モードのcompose.yamlと、issuerを決める KC_HOSTNAME の考え方
- kcadm.shでのレルム・ユーザー・OIDCクライアント作成と、groupsクレームの出し方
- oauth2-proxyでOIDC非対応アプリを保護し、グループで利用者を絞る構成
- ブルートフォース対策・MFA・日本語化の設定と、issuer不一致など実際のエラーの対処
Keycloakとは:位置づけとライセンス
Keycloakは現在Red Hatのエンジニアが中心となって開発しているプロジェクトで、CNCF(Cloud Native Computing Foundation)のプロジェクトでもあります。ライセンスはApache License 2.0なので、社内利用はもちろん商用製品に組み込むことも条件の範囲で可能で、有償サポートが必要な場合はRed Hatが「Red Hat build of Keycloak」として提供しています。
設定の単位は次の4つです。この関係を先に押さえておくと、管理コンソールで迷いません。
| 用語 | 意味 | 今回の例 |
|---|---|---|
| レルム(Realm) | ユーザー・グループ・クライアント・ログイン設定をまとめた独立した管理単位。テナントに近い | corp(社内用) |
| クライアント(Client) | Keycloakにログインを任せるアプリ1つ分の登録。リダイレクトURIや秘密鍵を持つ | wiki(社内Wiki) |
| グループ / ロール | 利用者の束と権限。トークンのクレームとしてアプリに渡せる | wiki-users |
| クライアントスコープ | トークンに載せる情報(メール、グループなど)の部品 | groups |
Entra IDやGoogle Workspaceをすでに使っている会社では、SaaSのSSOはそちらのIdPで受けるのが基本です(仕組みはSSO・SAML・OIDCの違いの解説を参照)。向いているのは、インターネットに出せない閉域の社内アプリをまとめたい、IDaaSのライセンスを全員分は買えない利用者(協力会社や工場の共用端末など)がいる、ログイン画面や認証フローを自社で作り込みたい、といった場合です。Entra IDを上流のIdPとしてKeycloakにつなぐ(Identity Brokering)こともできるため、「社員はEntra IDの資格情報で、社内アプリはKeycloak経由で」という二段構えも取れます。
構成を決める:本番モードとホスト名
Keycloakの起動コマンドには start-dev と start があります。start-dev は組み込みの開発用DBとHTTPで動く試用モードで、再作成するとデータが消えるため、社内で使う環境には start(本番モード)を使います。本番モードはTLSかリバースプロキシを前提にしていて、ホスト名の指定がないと起動しません。
もっとも大事な設計判断はホスト名です。OIDCでは、トークンに入る発行者(issuer)が https://sso.example.co.jp/realms/corp のようなURLになり、アプリ側は「設定したissuerと、Keycloakが名乗るissuerが一致するか」を検証します。ブラウザーからは外向きの名前、アプリのサーバーからはコンテナ名、とURLを使い分けるとこの検証に失敗するため、社内DNSで1つのFQDNを引けるようにして、ブラウザーもアプリもそのURLでKeycloakに到達させます。検証環境で KC_HOSTNAME を固定したところ、127.0.0.1 宛てにリクエストしてもディスカバリー文書のissuerは設定したホスト名のままでした。
公開する範囲も決めておきます。利用者のブラウザーが必要とするのは /realms/ と /resources/ だけで、管理コンソールとAdmin APIの /admin/ は情シスのネットワークからのみ届けば十分です。リバースプロキシで /admin/ を社内の管理用セグメントに限定し、ヘルスチェックとメトリクスが出る管理用ポート(9000)は外に公開しません。
Docker Composeで起動する
.env にDBのパスワードと初期管理者のパスワードを生成します。
mkdir -p ~/keycloak && cd ~/keycloak
printf 'KC_DB_PASSWORD=%s\nKC_BOOTSTRAP_ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 16)" "$(openssl rand -hex 12)" > .env
chmod 600 .envcompose.yamlは、TLSを手前のリバースプロキシで終端する前提の本番モード構成です。ローカルPCだけで試す場合は KC_HOSTNAME を http://localhost:8080 にし、KC_PROXY_HEADERS の行を消してください。
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
volumes:
- kc_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
interval: 5s
retries: 10
keycloak:
image: quay.io/keycloak/keycloak:26.7.4
command: start
restart: unless-stopped
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: ${KC_DB_PASSWORD}
KC_HOSTNAME: https://sso.example.co.jp
KC_PROXY_HEADERS: xforwarded
KC_HTTP_ENABLED: "true"
KC_HEALTH_ENABLED: "true"
KC_BOOTSTRAP_ADMIN_USERNAME: tmpadmin
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD}
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:9000:9000"
depends_on:
postgres:
condition: service_healthy
volumes:
kc_pgdata:KC_HTTP_ENABLED はコンテナ内をHTTPで待ち受ける指定で、KC_PROXY_HEADERS: xforwarded はプロキシが付ける X-Forwarded-Proto などを信用する指定です。後者を入れたときは、Keycloakのポートにプロキシ以外から直接届かないようにしておかないと、ヘッダーを偽装されます。起動したら管理用ポートで準備完了を待ちます。
docker compose up -d
curl -s http://127.0.0.1:9000/health/ready"status": "UP" が返れば起動完了です。検証環境(CPU負荷の高い4コアの仮想マシン)では、起動ログに「started in 20.284s」と出た後、初回だけmasterレルムの初期化にさらに52秒かかりました。start は起動のたびに設定のビルド処理も走るため、再起動を速くしたい場合は、kc.sh build を済ませたカスタムイメージを作って start --optimized で起動します。ローカル試用で KC_HOSTNAME を http:// にした場合は、起動ログに「WARNING: Likely misconfiguration detected. hostname is configured to use HTTP instead of HTTPS」と出ますが、試用なら問題ありません。
KC_BOOTSTRAP_ADMIN_* で作られるのは一時的な管理者です。管理コンソールにログインすると「You are logged in as a temporary admin user. To harden security, create a permanent admin account and delete the temporary one.」と表示されます。masterレルムに個人名の管理者を作ってadminロールを付け、ログインを確認したら tmpadmin は削除してください。.env の初期パスワードも、その時点で役目を終えます。
レルム・グループ・ユーザーを作る
社内アプリ用の設定はmasterレルムではなく、専用のレルムに作ります。masterは管理者のためのレルムで、ここにアプリや一般ユーザーを置くと、アプリの設定ミスが管理者権限の漏えいに直結するためです。管理コンソールでも操作できますが、手順を再現しやすいように kcadm.sh(管理用CLI、コンテナに同梱)で示します。
kcadm() { docker compose exec -T keycloak /opt/keycloak/bin/kcadm.sh "$@"; }
kcadm config credentials --server http://localhost:8080 --realm master --user tmpadmin --password "$(grep KC_BOOTSTRAP_ADMIN_PASSWORD .env | cut -d= -f2)"
kcadm create realms -s realm=corp -s enabled=true -s 'displayName=社内SSO'
kcadm create groups -r corp -s name=wiki-users
kcadm create users -r corp -s username=taro -s [email protected] -s firstName=太郎 -s lastName=山田 -s enabled=true -s emailVerified=true
kcadm set-password -r corp --username taro --new-password '初回だけ使うパスワード' --temporary--temporary を付けたパスワードは、初回ログイン時に本人が変更するまでしか使えません。ユーザーをグループに入れるには、ユーザーIDとグループIDを調べてから関連付けます。
USER_ID=$(kcadm get users -r corp -q username=taro -q exact=true --fields id --format csv --noquotes | tr -d '\r')
GROUP_ID=$(kcadm get groups -r corp -q search=wiki-users --fields id --format csv --noquotes | tr -d '\r')
kcadm update users/$USER_ID/groups/$GROUP_ID -r corp -n
kcadm get users/$USER_ID/groups -r corp --fields name最後のコマンドで "name" : "wiki-users" が返れば完了です。実運用では、ユーザーを1人ずつ作るより、Active DirectoryやLDAPを「User federation」でつなぐか、Entra IDを上流IdPにする形が中心になります。ユーザーをKeycloakに直接持たせるのは、社外の協力会社など、既存のディレクトリにいない人だけにすると管理が分散しません。
OIDCクライアントを登録する
アプリ1つにつき1つのクライアントを作ります。社内Wikiを https://wiki.example.co.jp で公開し、oauth2-proxyがコールバックを受ける前提の設定です。
CLIENT_ID=$(kcadm create clients -r corp -s clientId=wiki -s protocol=openid-connect \
-s publicClient=false -s standardFlowEnabled=true -s directAccessGrantsEnabled=false \
-s 'redirectUris=["https://wiki.example.co.jp/oauth2/callback"]' \
-s 'webOrigins=["https://wiki.example.co.jp"]' \
-s 'attributes."pkce.code.challenge.method"=S256' -i | tr -d '\r')
echo $CLIENT_ID設定の意味は次のとおりです。publicClient=false はクライアントシークレットを持つ「機密クライアント」で、サーバー側で動くアプリはこちらを選びます。directAccessGrantsEnabled=false は、アプリがユーザーのパスワードを直接受け取ってトークンに交換する方式(Resource Owner Password Credentials)を禁止する指定で、これを許すとMFAを迂回されます。リダイレクトURIはワイルドカードを使わず完全なURLで登録し、PKCEはS256を必須にします。-i を付けると作成したクライアントの内部ID(UUID)だけが出力されるので、以降の操作のために変数 CLIENT_ID に入れています。
次に、トークンにグループ名を載せるためのクライアントスコープを作り、クライアントに追加します。クライアント個別のマッパーでも同じ情報は出せますが、oauth2-proxyなど groups というスコープを要求してくるアプリがあるため、スコープとして作っておくほうが後で困りません。
SCOPE_ID=$(kcadm create client-scopes -r corp -s name=groups -s protocol=openid-connect -s 'attributes."include.in.token.scope"=true' -i | tr -d '\r')
kcadm create client-scopes/$SCOPE_ID/protocol-mappers/models -r corp \
-s name=groups -s protocol=openid-connect -s protocolMapper=oidc-group-membership-mapper \
-s 'config."claim.name"=groups' -s 'config."full.path"=false' \
-s 'config."id.token.claim"=true' -s 'config."access.token.claim"=true' -s 'config."userinfo.token.claim"=true'
kcadm update clients/$CLIENT_ID/optional-client-scopes/$SCOPE_ID -r corp
kcadm get clients/$CLIENT_ID/client-secret -r corpfull.path=false にすると、グループ名が /wiki-users ではなく wiki-users で入ります。最後のコマンドで表示されるクライアントシークレットは、oauth2-proxyの設定に使う秘密情報です。26.7.4では86文字の長い文字列が発行されました。
OIDC非対応の社内アプリをoauth2-proxyで守る
GrafanaやGitLabのようにOIDCに対応したアプリは、アプリの設定画面にissuer・クライアントID・シークレットを入れればKeycloakとつながります。問題はログイン機能を持たない社内ツールや、ベーシック認証しかない古いアプリです。これらは、OIDCのログインを代行するoauth2-proxyを手前に置くと、Keycloakでログインした人だけを通せます。
services:
wiki:
image: traefik/whoami:v1.12.0 # 保護したい社内アプリの代わり(受け取ったヘッダーを表示する)
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
restart: unless-stopped
environment:
OAUTH2_PROXY_PROVIDER: keycloak-oidc
OAUTH2_PROXY_OIDC_ISSUER_URL: https://sso.example.co.jp/realms/corp
OAUTH2_PROXY_CLIENT_ID: wiki
OAUTH2_PROXY_CLIENT_SECRET: ${WIKI_CLIENT_SECRET}
OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256
OAUTH2_PROXY_REDIRECT_URL: https://wiki.example.co.jp/oauth2/callback
OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}
OAUTH2_PROXY_EMAIL_DOMAINS: example.co.jp
OAUTH2_PROXY_ALLOWED_GROUPS: wiki-users
OAUTH2_PROXY_UPSTREAMS: http://wiki:80
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_PASS_USER_HEADERS: "true"
ports:
- "127.0.0.1:4180:4180"OAUTH2_PROXY_COOKIE_SECRET は openssl rand -hex 16 で作った32文字をそのまま使えます。OAUTH2_PROXY_ALLOWED_GROUPS を指定すると、Keycloakでの認証に成功しても wiki-users に属していない人は403で止まります。リバースプロキシからは wiki.example.co.jp をこのコンテナの4180番に転送します。
検証では、ブラウザーで保護対象のURLを開くとoauth2-proxyの「Sign in with Keycloak OIDC」ボタン、Keycloakの「社内SSO」ログイン画面の順に遷移し、レルムに作ったユーザー(検証ではtaro)でログインすると保護対象のアプリに戻りました。アプリに届いたヘッダーは次のとおりです。
X-Forwarded-Email: [email protected]
X-Forwarded-Groups: wiki-users,role:default-roles-corp,role:offline_access,role:uma_authorization,role:account:manage-account,role:account:manage-account-links,role:account:view-profile
X-Forwarded-Preferred-Username: taro
X-Forwarded-User: b4e13644-f02c-49c7-b431-90d70033e5b9keycloak-oidc プロバイダーは、グループに加えてロールも role: を付けて渡します。アプリ側で利用者を識別するときは X-Forwarded-User(KeycloakのユーザーID)かメールアドレスを使い、表示名に頼らないようにします。グループに入れていないユーザーでログインすると、画面は403 Forbiddenになり、oauth2-proxyのログには「[AuthFailure] Invalid authentication via OAuth2: unauthorized」が残りました。
保護対象のアプリがこれらのヘッダーを信用して利用者を判定する場合、アプリにoauth2-proxyを通らずに届く経路があると、誰でも X-Forwarded-User を名乗れてしまいます。アプリのポートはDockerの内部ネットワークだけに置き、ホストにもLANにも公開しないでください。compose.app.yamlで wiki サービスに ports を書いていないのはそのためです。
セキュリティ設定:ブルートフォース・パスワード・MFA・日本語化
kcadm で作ったレルムの初期値を確認すると、26.7.4ではブルートフォース検知が無効(bruteForceProtected: false)、パスワードポリシーなし、国際化(多言語表示)も無効で、ログイン画面は英語でした。セッションはアイドル30分・最大10時間、アクセストークンの有効期間は5分です。最低限、次の設定を入れます。
kcadm update realms/corp -s bruteForceProtected=true -s failureFactor=5 \
-s 'passwordPolicy=length(12) and notUsername and notEmail' \
-s internationalizationEnabled=true -s 'supportedLocales=["ja","en"]' -s defaultLocale=ja
kcadm update authentication/required-actions/CONFIGURE_TOTP -r corp -s defaultAction=truefailureFactor=5 は、5回続けてパスワードを間違えると一時ロックする設定です。日本語を有効にすると、ログイン画面は「アカウントにサインイン」「ユーザー名またはメールアドレス」の表記になり、言語切り替えのメニューも出ます。
TOTP(認証アプリのワンタイムパスワード)の設定を「既定のアクション(Default action)」にすると、以後に作成したユーザーは初回ログイン時に「モバイルオーセンティケーターのセットアップ」画面へ誘導されます。ただし検証したところ、既存ユーザーには適用されませんでした。すでに作ったユーザーには、ユーザーごとに必須アクションを付けます。
for id in $(kcadm get users -r corp --fields id --format csv --noquotes | tr -d '\r'); do
kcadm update users/$id -r corp -s 'requiredActions=["CONFIGURE_TOTP"]'
doneこのループはユーザーに付いている他の必須アクションを上書きするため、パスワード変更を求めている最中のユーザーがいる場合は、対象を絞って実行してください。認証アプリの配布が難しい部署がある場合は、WebAuthn(パスキーやセキュリティキー)の必須アクションも用意されています。
運用:バックアップ・エクスポート・更新
バックアップの基本はPostgreSQLのダンプです。レルム設定、ユーザー、クライアント、パスワードのハッシュまですべてDBに入っています。
docker compose exec postgres pg_dump -U keycloak -d keycloak -Fc -f /tmp/keycloak.dump
docker compose cp postgres:/tmp/keycloak.dump ./backup/keycloak-$(date +%F).dumpこれとは別に、レルムの設定をJSONで書き出しておくと、変更の差分確認や検証環境への複製に使えます。起動中のコンテナ内で kc.sh export を実行すると、書き出し自体は終わるものの、管理用ポートの二重起動で「Unable to start the management interface on 0.0.0.0:9000」「Address already in use」というエラーが出ました。使い捨てのコンテナで実行するのが確実です。
mkdir -p export && sudo chown 1000:0 export
docker compose run --rm --no-deps -v ./export:/export keycloak export --dir /export --realm corp --users realm_file
ls -l export/corp-realm.jsonコンテナ内のKeycloakはUID 1000で動くため、書き出し先のディレクトリの所有者を合わせないと「Error during export/import: /export/corp-realm.json (Permission denied)」で失敗します。書き出したJSONにはクライアントシークレットとパスワードのハッシュが含まれていたので、DBダンプと同じ扱いで保管してください。
更新はマイナーバージョンでも挙動が変わることがあるため、イメージのタグを固定し、公式のアップグレードガイドで該当バージョンの変更点を読んでから、DBダンプを取ったうえで1段ずつ上げます。検証環境でレルムのJSONを読み込み、ログインとクライアント連携を確かめてから本番に適用する流れにしておくと、ログインできない時間帯を作らずに済みます。
動作確認
設定が意図どおりかは、ディスカバリー文書とセッション一覧で確認できます。
curl -s https://sso.example.co.jp/realms/corp/.well-known/openid-configuration | jq '{issuer, authorization_endpoint, code_challenge_methods_supported}'
kcadm get clients/$CLIENT_ID/user-sessions -r corp1つ目で issuer が https://sso.example.co.jp/realms/corp になっていること、code_challenge_methods_supported に S256 が含まれることを確認します。検証環境(ホスト名を kc.lab.test にした構成)では、issuer・認可・トークン・JWKSのURLがすべてそのホスト名で返りました。2つ目は、ブラウザーで保護対象のアプリにログインした後に実行し、ログインしたユーザー名とクライアント名 wiki が並べば、SSOの往復が成立しています。管理コンソールでは、corpレルムの「Sessions」に同じ内容が表示されます。
最後に、グループに入れていないユーザーで同じURLを開き、Keycloakでのログインには成功するのにアプリには403で入れないことを確かめます。ここまで確認できれば、「認証はKeycloak、利用可否はグループ」という役割分担が機能しています。
つまずきポイント
oauth2-proxyが「oidc: issuer did not match the issuer returned by provider」で起動しない
検証では、issuerにコンテナ名のURLを指定したoauth2-proxyが「expected "http://keycloak:8080/realms/corp" got "http://kc.lab.test:8080/realms/corp"」というエラーで起動を止めました。issuerには KC_HOSTNAME のURLが使われるので、アプリ側にも同じURLを設定します。コンテナからそのFQDNが引けない場合は、社内DNSに登録するか、Composeのネットワークエイリアスで同じ名前をKeycloakのコンテナに付けます。
ログイン後に「error=invalid_scope」「Invalid scopes: openid email profile groups」で戻される
oauth2-proxyの keycloak-oidc プロバイダーは groups スコープを要求しますが、Keycloakの既定のクライアントスコープにはこの名前がありません。前述の手順で groups という名前のクライアントスコープを作り、クライアントにオプションとして追加すると解消します。スコープ名とマッパーのクレーム名は、どちらも groups にそろえておきます。
ログイン画面の代わりに「Invalid parameter: redirect_uri」と表示される
アプリが送ってきたリダイレクトURIが、クライアントに登録したものと1文字でも違うと出ます。http と https の違い、末尾のスラッシュ、ポート番号、/oauth2/callback と /callback のようなパスの違いが典型です。アプリのログか、ブラウザーのアドレスバーに出ている認可リクエストの redirect_uri パラメーターを見て、登録内容をそれに合わせます。
「error=invalid_request」「Missing parameter: code_challenge_method」で戻される
クライアントでPKCE(S256)を必須にしているのに、アプリ側がPKCEのパラメーターを送っていないときに出ます。oauth2-proxyなら OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256 を入れます。PKCEに対応していない古いアプリの場合は、そのクライアントだけPKCEの必須を外す代わりに、リダイレクトURIを厳密に絞り、クライアントシークレットの管理を徹底してください。
よくある質問
Keycloakとは何ですか?
OIDCやSAMLでシングルサインオンを提供する、オープンソースのID・アクセス管理サーバーです。読み方はキークロークです。ユーザー・グループの管理、MFA、ActiveDirectoryやLDAPとの連携、外部IdP(Entra IDやGoogle)との連携ができ、社内アプリのログインを1か所に集約できます。
OSSのKeycloakは商用利用できますか?
はい。Apache License 2.0のオープンソースソフトウェアで、社内利用・商用利用とも可能です。ライセンス費用はかかりませんが、障害時の問い合わせ先が必要な場合は、Red Hatが提供する有償サポート付きの「Red Hat build of Keycloak」を検討します。どちらを選んでも、運用とセキュリティ更新は自社の責任です。
Keycloakにはどんな脆弱性がありますか?
製品自体の脆弱性はGitHubのセキュリティアドバイザリーなどで公表され、更新版で修正されるため、バージョンを追い続けることが第一の対策です。実際の事故につながりやすいのは設定の方で、管理コンソールの外部公開、masterレルムへのアプリ登録、start-dev のまま本番運用、ブルートフォース検知の無効化は避けてください。
AWSにおけるKeycloakとは何ですか?
AWSが提供するサービスではなく、EC2やECS、EKSの上で自分で動かすソフトウェアです。AWSで同じ役割のマネージドサービスはAmazon Cognitoです。AWSのコンソールへのログインをKeycloakでまとめたい場合は、KeycloakをSAMLのIdPとしてIAM Identity Centerにつなぐ構成が取れます。
まとめ
KeycloakでSSO基盤を作るときは、本番モードで起動し、ブラウザーとアプリが同じURLで到達できるホスト名を KC_HOSTNAME に決めることが出発点です。アプリごとに機密クライアントを作ってPKCEとリダイレクトURIを厳密にし、利用可否はグループで判定します。OIDC非対応のアプリはoauth2-proxyで包み、アプリのポートは外に出しません。ブルートフォース検知とTOTPは既存ユーザーへの適用漏れまで確認してください。
次に読む
- SSO・SAML・OIDCの違いをやさしく解説:issuer、リダイレクト、トークンの流れを図解で整理
- nginxでリバースプロキシを構築する:KeycloakのTLS終端と
/admin/の公開制限 - Nextcloudで社内ファイル共有を構築する:Keycloakのクライアントを追加して、ファイル共有にもSSOを広げる