はじめに
Keycloakで複数のRealmを管理する際、共通のAuthentication Flowを適用する場合には、GUIで設定するよりもJSONファイルをインポート形式で利用する方法が効率的です。これにより、設定を再利用しやすくなり、運用コストが削減できます。ただし、公式のkc.[sh | bat] importコマンドを利用すると、Keycloakを一時停止する必要があります。本記事では、アプリケーションを停止せずに管理コンソールからJSONファイルを使ってRealmを作成する方法を紹介します。 |
背景
Keycloakでユーザーのログイン体験をカスタマイズするために、ユーザー名とパスワードを別々のステップで入力させるAuthentication Flowを設定したいと考えました。このようなカスタムのAuthentication Flowを設定するためには、既存のFlowをコピーして編集するか、新たにFlowを作成する必要があります。
ただし、Keycloakの管理コンソールから部分的なインポート(Partial Import)を行う場合、Authentication Flowを含めることができません。そのため、JSONファイルを使用してRealm全体をインポートする方法を取ります。
手順
-
管理コンソールへのログイン
Keycloakの管理コンソールにAdminユーザーでログインします。
-
Realmの作成
左上の「Create realm」ボタンをクリックし、「Browse…」から事前に用意したJSONファイルを選択してインポートします。
-
注意点
-
部分的なインポート(Realm Settings > Import)では、Authentication Flowをインポート・変更することはできません。インポート可能なのは以下の6種類のみです。
- Users
- Groups
- Clients
- Identity Providers
- Roles (Realm Roles)
- Roles (Client Roles)
ソースコードはこちらで確認できます。
-
JSONファイル内で設定したいFlowは
"browserFlow": "separate-browser-step"
です。関連するAuthentication Flowは以下の通りです。separate-browser-step
separate-browser-step Browser - Conditional OTP
separate-browser-step forms
-
JSONファイルのサンプル
以下に、インポート時に使用するJSONファイルのサンプルを示します。
JSONファイルを表示
{
"realm": "import-realm-v1", ## realmName適宜書き換え
"enabled": true,
"authenticationFlows": [
{
"id": "3d50274b-05a1-4f2e-ae23-da0f7e6d974a", ## UUID DB内でユニークである必要がある
"alias": "Account verification options",
"description": "Method with which to verity the existing account",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "idp-email-verification",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "ALTERNATIVE",
"priority": 20,
"autheticatorFlow": true,
"flowAlias": "Verify Existing Account by Re-authentication",
"userSetupAllowed": false
}
]
},
{
"id": "5200230f-9465-42f9-9748-26ec2afddb9c", ## UUID DB内でユニークである必要がある
"alias": "Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "conditional-user-configured",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "auth-otp-form",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "86192052-fd70-40fc-8b3d-0cfc717cb418", ## UUID DB内でユニークである必要がある
"alias": "Direct Grant - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "conditional-user-configured",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "direct-grant-validate-otp",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "022835e2-ad89-4a96-ac30-468857c60b4a", ## UUID DB内でユニークである必要がある
"alias": "First broker login - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "conditional-user-configured",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "auth-otp-form",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "a4943d0b-d7ff-44cd-9cee-60f50e70da12", ## UUID DB内でユニークである必要がある
"alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "idp-confirm-link",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": true,
"flowAlias": "Account verification options",
"userSetupAllowed": false
}
]
},
{
"id": "778e7529-1021-46cf-8c58-c2a97f7cbb92", ## UUID DB内でユニークである必要がある
"alias": "Reset - Conditional OTP",
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "conditional-user-configured",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "reset-otp",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "fda322a7-01d0-4635-a9a2-980636ca1fd4", ## UUID DB内でユニークである必要がある
"alias": "User creation or linking",
"description": "Flow for the existing/non-existing user alternatives",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticatorConfig": "create unique user config",
"authenticator": "idp-create-user-if-unique",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "ALTERNATIVE",
"priority": 20,
"autheticatorFlow": true,
"flowAlias": "Handle Existing Account",
"userSetupAllowed": false
}
]
},
{
"id": "f6db95ab-14bd-41ea-b624-520c83c1297c", ## UUID DB内でユニークである必要がある
"alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "idp-username-password-form",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "CONDITIONAL",
"priority": 20,
"autheticatorFlow": true,
"flowAlias": "First broker login - Conditional OTP",
"userSetupAllowed": false
}
]
},
{
"id": "2b6c2f3d-fffa-4d25-8201-97a71f9fd9e1", ## UUID DB内でユニークである必要がある
"alias": "browser",
"description": "browser based authentication",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "auth-cookie",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "auth-spnego",
"authenticatorFlow": false,
"requirement": "DISABLED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "identity-provider-redirector",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 25,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "ALTERNATIVE",
"priority": 30,
"autheticatorFlow": true,
"flowAlias": "forms",
"userSetupAllowed": false
}
]
},
{
"id": "40b248d1-efb4-40b5-bea2-30fc5c36e248", ## UUID DB内でユニークである必要がある
"alias": "clients",
"description": "Base authentication for clients",
"providerId": "client-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "client-secret",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "client-jwt",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "client-secret-jwt",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 30,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "client-x509",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 40,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "c574a4af-be52-40ed-a12c-6cd8ea8c1ec7", ## UUID DB内でユニークである必要がある
"alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "direct-grant-validate-username",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "direct-grant-validate-password",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "CONDITIONAL",
"priority": 30,
"autheticatorFlow": true,
"flowAlias": "Direct Grant - Conditional OTP",
"userSetupAllowed": false
}
]
},
{
"id": "84eec2af-1279-4f61-b1fd-59b0173b2ac8", ## UUID DB内でユニークである必要がある
"alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "docker-http-basic-authenticator",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "fcfe08ff-a0a5-48f4-bf0a-73873376d03d", ## UUID DB内でユニークである必要がある
"alias": "forms",
"description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "auth-username-password-form",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "CONDITIONAL",
"priority": 20,
"autheticatorFlow": true,
"flowAlias": "Browser - Conditional OTP",
"userSetupAllowed": false
}
]
},
{
"id": "cabbc50c-2dc9-4123-bd73-b50509d6f7cf", ## UUID DB内でユニークである必要がある
"alias": "separate-browser-step",
"description": "browser based authentication",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": false,
"authenticationExecutions": [
{
"authenticator": "auth-cookie",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "auth-spnego",
"authenticatorFlow": false,
"requirement": "DISABLED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "identity-provider-redirector",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
"priority": 25,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "ALTERNATIVE",
"priority": 30,
"autheticatorFlow": true,
"flowAlias": "separate-browser-step forms",
"userSetupAllowed": false
}
]
},
{
"id": "02dea47e-2a0c-4c64-a33a-d1f81805bd8d", ## UUID DB内でユニークである必要がある
"alias": "separate-browser-step Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": false,
"authenticationExecutions": [
{
"authenticator": "conditional-user-configured",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "auth-otp-form",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "432d1de5-e544-4f42-98be-261f3bd915b8", ## UUID DB内でユニークである必要がある
"alias": "separate-browser-step forms",
"description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow",
"topLevel": false,
"builtIn": false,
"authenticationExecutions": [
{
"authenticator": "auth-username-form",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "auth-password-form",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 21,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "CONDITIONAL",
"priority": 22,
"autheticatorFlow": true,
"flowAlias": "separate-browser-step Browser - Conditional OTP",
"userSetupAllowed": false
}
]
},
{
"id": "c107b47b-e637-4b67-bd1e-54b7b92f82ff", ## UUID DB内でユニークである必要がある
"alias": "registration",
"description": "registration flow",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "registration-page-form",
"authenticatorFlow": true,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": true,
"flowAlias": "registration form",
"userSetupAllowed": false
}
]
},
{
"id": "62b795d1-e8e8-445b-b11f-72ebe1cba55e", ## UUID DB内でユニークである必要がある
"alias": "registration form",
"description": "registration form",
"providerId": "form-flow",
"topLevel": false,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "registration-user-creation",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "registration-profile-action",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 40,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "registration-password-action",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 50,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "registration-recaptcha-action",
"authenticatorFlow": false,
"requirement": "DISABLED",
"priority": 60,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
},
{
"id": "75ed89b5-c11b-422a-a49f-e411cf90b0cd", ## UUID DB内でユニークである必要がある
"alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "reset-credentials-choose-user",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "reset-credential-email",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 20,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticator": "reset-password",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 30,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{
"authenticatorFlow": true,
"requirement": "CONDITIONAL",
"priority": 40,
"autheticatorFlow": true,
"flowAlias": "Reset - Conditional OTP",
"userSetupAllowed": false
}
]
},
{
"id": "9472706f-fe79-41c8-9c12-71e59eaebfa6", ## UUID DB内でユニークである必要がある
"alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": true,
"authenticationExecutions": [
{
"authenticator": "http-basic-authenticator",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 10,
"autheticatorFlow": false,
"userSetupAllowed": false
}
]
}
],
"authenticatorConfig": [
{
"id": "36a8a30b-7e3a-4dfc-a1fe-fec543a97ae8", ## UUID DB内でユニークである必要がある
"alias": "create unique user config",
"config": {
"require.password.update.after.registration": "false"
}
},
{
"id": "74d60b81-5864-4ca0-ab53-984116890960", ## UUID DB内でユニークである必要がある
"alias": "review profile config",
"config": {
"update.profile.on.first.login": "missing"
}
}
],
"browserFlow": "separate-browser-step", ## 適用したいFlow Name
"registrationFlow": "registration",
"directGrantFlow": "direct grant",
"resetCredentialsFlow": "reset credentials",
"clientAuthenticationFlow": "clients",
"dockerAuthenticationFlow": "docker auth"
}
注意点
authenticationFlows
内の各Flowには一意のid
(UUID)が必要です。他のRealmや既存のFlowと重複しないように注意してください。authenticatorConfig
も同様に一意のid
が必要です。
解説
JSONファイルでRealmをインポートする際、authenticationFlows
セクションでカスタムのAuthentication Flowを定義できます。ここで指定したFlowをbrowserFlow
などの設定で適用することで、ログインフローをカスタマイズできます。
importAuthenticationFlows
メソッド(ソースコード)が、インポート時にAuthentication Flowを処理しています。
まとめ
KeycloakのRealmをJSONファイルでインポートすることで、アプリケーションを停止せずにカスタムのAuthentication Flowを設定できます。これにより、より柔軟なログインフローの構築が可能となります。
ポイント
- 管理コンソールからのRealm作成時にJSONファイルを使用する。
authenticationFlows
セクションで独自のFlowを定義する。id
(UUID)は一意の値を使用する。- 部分的なインポートではAuthentication Flowを変更できない。