Replacing keycloak with {project_name}
This commit is contained in:
parent
7bacbbda67
commit
db81d66e24
27 changed files with 343 additions and 233 deletions
|
@ -16,7 +16,7 @@ In addition to that, it is possible to implement any functionality that initiate
|
||||||
using action token SPI, details of which are described in the text below.
|
using action token SPI, details of which are described in the text below.
|
||||||
|
|
||||||
[[_action_token_anatomy]]
|
[[_action_token_anatomy]]
|
||||||
=== Anatomy of Action Token
|
=== Anatomy of action token
|
||||||
|
|
||||||
Action token is a standard Json Web Token signed with active realm key where the payload contains several fields:
|
Action token is a standard Json Web Token signed with active realm key where the payload contains several fields:
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ Action token is a standard Json Web Token signed with active realm key where the
|
||||||
|
|
||||||
In addition, an action token can contain any number of custom fields serializable into JSON.
|
In addition, an action token can contain any number of custom fields serializable into JSON.
|
||||||
|
|
||||||
=== Action Token Processing
|
=== Action token processing
|
||||||
|
|
||||||
When an action token is passed to a {project_name} endpoint
|
When an action token is passed to a {project_name} endpoint
|
||||||
`_KEYCLOAK_ROOT_/auth/realms/master/login-actions/action-token` via `key` parameter, it is validated and a proper action
|
`_KEYCLOAK_ROOT_/auth/realms/master/login-actions/action-token` via `key` parameter, it is validated and a proper action
|
||||||
|
@ -60,9 +60,9 @@ action according to parameters in the token.
|
||||||
5. *Invalidation of single-Use tokens.* If the token is set to single-use, once the authentication flow finishes, the
|
5. *Invalidation of single-Use tokens.* If the token is set to single-use, once the authentication flow finishes, the
|
||||||
action token is invalidated.
|
action token is invalidated.
|
||||||
|
|
||||||
=== Implement Your Own Action Token and its Handler
|
=== Implement your own action token and its handler
|
||||||
|
|
||||||
==== How to Create an Action Token
|
==== How to create an action token
|
||||||
|
|
||||||
As action token is just a signed JWT with few mandatory fields (see <<_action_token_anatomy,Anatomy of action token>>
|
As action token is just a signed JWT with few mandatory fields (see <<_action_token_anatomy,Anatomy of action token>>
|
||||||
above), it can be serialized and signed as such using Keycloak's `JWSBuilder` class. This way has been already
|
above), it can be serialized and signed as such using Keycloak's `JWSBuilder` class. This way has been already
|
||||||
|
@ -131,7 +131,7 @@ public class DemoActionToken extends DefaultActionToken {
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
==== Packaging Classes and Deployment
|
==== Packaging classes and deployment
|
||||||
|
|
||||||
To plug your own action token and its handler, you need to implement few interfaces on server side:
|
To plug your own action token and its handler, you need to implement few interfaces on server side:
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,24 @@
|
||||||
|
|
||||||
{project_name} comes with a fully functional Admin REST API with all features provided by the Admin Console.
|
{project_name} comes with a fully functional Admin REST API with all features provided by the Admin Console.
|
||||||
|
|
||||||
To invoke the API you need to obtain an access token with the appropriate permissions. The required permissions are described in
|
To invoke the API you need to obtain an access token with the appropriate permissions. The required permissions are described in the Server Administration Guide.
|
||||||
{adminguide_link}[{adminguide_name}].
|
|
||||||
|
|
||||||
A token can be obtained by enabling authenticating to your application with {project_name}; see the
|
A token can be obtained by enabling authenticating to your application with {project_name}; see the Securing Applications and Services Guide. You can also use direct access grant to obtain an access token.
|
||||||
{adapterguide_link}[{adapterguide_name}]. You can also use direct access grant to obtain an access token.
|
|
||||||
|
|
||||||
For complete documentation see {apidocs_link}[{apidocs_name}].
|
.Additional resources
|
||||||
|
[role="_additional-resources"]
|
||||||
|
* {adminguide_link}[{adminguide_name}]
|
||||||
|
* {adapterguide_link}[{adapterguide_name}]
|
||||||
|
* {apidocs_link}[{apidocs_name}]
|
||||||
|
|
||||||
=== Examples using CURL
|
=== Examples of using CURL
|
||||||
|
|
||||||
==== Authenticate with username and password
|
==== Authenticate with username and password
|
||||||
|
|
||||||
Obtain access token for user in the realm `master` with username `admin` and password `password`:
|
.Procedure
|
||||||
|
|
||||||
|
. Obtain an access token for user in the realm `master` with username `admin` and password `password`:
|
||||||
|
+
|
||||||
[source,bash]
|
[source,bash]
|
||||||
----
|
----
|
||||||
curl \
|
curl \
|
||||||
|
@ -24,14 +29,17 @@ curl \
|
||||||
-d "grant_type=password" \
|
-d "grant_type=password" \
|
||||||
"http://localhost:8080/auth/realms/master/protocol/openid-connect/token"
|
"http://localhost:8080/auth/realms/master/protocol/openid-connect/token"
|
||||||
----
|
----
|
||||||
|
+
|
||||||
NOTE: By default this token expires in 1 minute
|
NOTE: By default this token expires in 1 minute
|
||||||
|
+
|
||||||
|
The result will be a JSON document.
|
||||||
|
|
||||||
The result will be a JSON document. To invoke the API you need to extract the value of the `access_token` property. You can then invoke the API by including
|
. Invoke the API you need by extracting the value of the `access_token` property.
|
||||||
the value in the `Authorization` header of requests to the API.
|
|
||||||
|
|
||||||
|
. Invoke the API by including the value in the `Authorization` header of requests to the API.
|
||||||
|
+
|
||||||
The following example shows how to get the details of the master realm:
|
The following example shows how to get the details of the master realm:
|
||||||
|
+
|
||||||
[source,bash]
|
[source,bash]
|
||||||
----
|
----
|
||||||
curl \
|
curl \
|
||||||
|
@ -71,6 +79,7 @@ The following example shows how to use the Java client library to get the detail
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
|
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
...
|
...
|
||||||
|
@ -86,3 +95,4 @@ RealmRepresentation realm = keycloak.realm("master").toRepresentation();
|
||||||
|
|
||||||
Complete Javadoc for the admin client is available at {apidocs_link}[{apidocs_name}].
|
Complete Javadoc for the admin client is available at {apidocs_link}[{apidocs_name}].
|
||||||
endif::[]
|
endif::[]
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
{project_name} includes a range of different authentication mechanisms: kerberos, password, otp and others.
|
{project_name} includes a range of different authentication mechanisms: kerberos, password, otp and others.
|
||||||
These mechanisms may not meet all of your requirements and you may want to plug in your own custom ones.
|
These mechanisms may not meet all of your requirements and you may want to plug in your own custom ones.
|
||||||
{project_name} provides an authentication SPI that you can use to write new plugins.
|
{project_name} provides an authentication SPI that you can use to write new plugins.
|
||||||
The admin console supports applying, ordering, and configuring these new mechanisms.
|
The Admin Console supports applying, ordering, and configuring these new mechanisms.
|
||||||
|
|
||||||
{project_name} also supports a simple registration form.
|
{project_name} also supports a simple registration form.
|
||||||
Different aspects of this form can be enabled and disabled i.e.
|
Different aspects of this form can be enabled and disabled for example
|
||||||
Recaptcha support can be turned off and on.
|
Recaptcha support can be turned off and on.
|
||||||
The same authentication SPI can be used to add another page to the registration flow or reimplement it entirely.
|
The same authentication SPI can be used to add another page to the registration flow or reimplement it entirely.
|
||||||
There's also an additional fine-grained SPI you can use to add specific validations and user extensions to the built in registration form.
|
There's also an additional fine-grained SPI you can use to add specific validations and user extensions to the built in registration form.
|
||||||
|
@ -26,7 +26,7 @@ To first learn about the Authentication SPI, let's go over some of the terms use
|
||||||
|
|
||||||
Authentication Flow::
|
Authentication Flow::
|
||||||
A flow is a container for all authentications that must happen during login or registration.
|
A flow is a container for all authentications that must happen during login or registration.
|
||||||
If you go to the admin console authentication page, you can view all the defined flows in the system and what authenticators they are made up of.
|
If you go to the Admin Console authentication page, you can view all the defined flows in the system and what authenticators they are made up of.
|
||||||
Flows can contain other flows.
|
Flows can contain other flows.
|
||||||
You can also bind a new different flow for browser login, direct grant access, and registration.
|
You can also bind a new different flow for browser login, direct grant access, and registration.
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ Required Action::
|
||||||
After authentication completes, the user might have one or more one-time actions he must complete before he is allowed to login.
|
After authentication completes, the user might have one or more one-time actions he must complete before he is allowed to login.
|
||||||
The user might be required to set up an OTP token generator or reset an expired password or even accept a Terms and Conditions document.
|
The user might be required to set up an OTP token generator or reset an expired password or even accept a Terms and Conditions document.
|
||||||
|
|
||||||
=== Algorithm Overview
|
=== Algorithm overview
|
||||||
|
|
||||||
Let's talk about how this all works for browser login.
|
Let's talk about how this all works for browser login.
|
||||||
Let's assume the following flows, executions and sub flows.
|
Let's assume the following flows, executions and sub flows.
|
||||||
|
@ -146,7 +146,7 @@ Let's walk through the steps from when a client first redirects to keycloak to a
|
||||||
. After all required actions have been resolved, the user is finally logged in.
|
. After all required actions have been resolved, the user is finally logged in.
|
||||||
|
|
||||||
[[_auth_spi_walkthrough]]
|
[[_auth_spi_walkthrough]]
|
||||||
=== Authenticator SPI Walk Through
|
=== Authenticator SPI walk through
|
||||||
|
|
||||||
In this section, we'll take a look at the Authenticator interface.
|
In this section, we'll take a look at the Authenticator interface.
|
||||||
For this, we are going to implement an authenticator that requires that a user enter in the answer to a secret question like "What is your mother's maiden name?".
|
For this, we are going to implement an authenticator that requires that a user enter in the answer to a secret question like "What is your mother's maiden name?".
|
||||||
|
@ -169,7 +169,7 @@ These types of authenticators are called CredentialValidators, and will require
|
||||||
|
|
||||||
The SecretQuestionAuthenticator we'll see in this walk through is a CredentialValidator, so we'll see how to implement all these classes.
|
The SecretQuestionAuthenticator we'll see in this walk through is a CredentialValidator, so we'll see how to implement all these classes.
|
||||||
|
|
||||||
==== Packaging Classes and Deployment
|
==== Packaging classes and deployment
|
||||||
|
|
||||||
You will package your classes within a single jar.
|
You will package your classes within a single jar.
|
||||||
This jar must contain a file named `org.keycloak.authentication.AuthenticatorFactory` and must be contained in the `META-INF/services/` directory of your jar.
|
This jar must contain a file named `org.keycloak.authentication.AuthenticatorFactory` and must be contained in the `META-INF/services/` directory of your jar.
|
||||||
|
@ -218,7 +218,7 @@ Where:
|
||||||
* `created_date` is the creation timestamp (in long format) of the credential.
|
* `created_date` is the creation timestamp (in long format) of the credential.
|
||||||
* `user_label` is the editable name of the credential by the user
|
* `user_label` is the editable name of the credential by the user
|
||||||
* `secret_data` contains a static json with the information that cannot be transmitted outside of {project_name}
|
* `secret_data` contains a static json with the information that cannot be transmitted outside of {project_name}
|
||||||
* `credential_data` contains a json with the static information of the credential that can be shared in the admin console or via the REST API.
|
* `credential_data` contains a json with the static information of the credential that can be shared in the Admin Console or via the REST API.
|
||||||
* `priority` defines how "preferred" a credential is for a user, to determine which credential to present when a user has multiple choices.
|
* `priority` defines how "preferred" a credential is for a user, to determine which credential to present when a user has multiple choices.
|
||||||
|
|
||||||
As the secret_data and credential_data fields are designed to contain json, it is up to you to determine how to structure, read and write into
|
As the secret_data and credential_data fields are designed to contain json, it is up to you to determine how to structure, read and write into
|
||||||
|
@ -479,7 +479,7 @@ public boolean isConfiguredFor(RealmModel realm, UserModel user, String credenti
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
==== Implementing an Authenticator
|
==== Implementing an authenticator
|
||||||
|
|
||||||
When implementing an authenticator that uses Credentials to authenticate a user, you should have the authenticator implement
|
When implementing an authenticator that uses Credentials to authenticate a user, you should have the authenticator implement
|
||||||
the CredentialValidator interface. This interfaces takes a class extending a CredentialProvider as a parameter, and will
|
the CredentialValidator interface. This interfaces takes a class extending a CredentialProvider as a parameter, and will
|
||||||
|
@ -644,7 +644,7 @@ protected void setCookie(AuthenticationFlowContext context) {
|
||||||
We obtain an AuthenticatorConfigModel from the AuthenticationFlowContext.getAuthenticatorConfig() method.
|
We obtain an AuthenticatorConfigModel from the AuthenticationFlowContext.getAuthenticatorConfig() method.
|
||||||
If configuration exists we pull the max age config out of it.
|
If configuration exists we pull the max age config out of it.
|
||||||
We will see how we can define what should be configured when we talk about the AuthenticatorFactory implementation.
|
We will see how we can define what should be configured when we talk about the AuthenticatorFactory implementation.
|
||||||
The config values can be defined within the admin console if you set up config definitions in your AuthenticatorFactory implementation.
|
The config values can be defined within the Admin Console if you set up config definitions in your AuthenticatorFactory implementation.
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
|
@ -699,7 +699,7 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
|
||||||
|
|
||||||
The next thing the factory is responsible for is to specify the allowed requirement switches.
|
The next thing the factory is responsible for is to specify the allowed requirement switches.
|
||||||
While there are four different requirement types: ALTERNATIVE, REQUIRED, CONDITIONAL, DISABLED, AuthenticatorFactory implementations can limit which
|
While there are four different requirement types: ALTERNATIVE, REQUIRED, CONDITIONAL, DISABLED, AuthenticatorFactory implementations can limit which
|
||||||
requirement options are shown in the admin console when defining a flow. CONDITIONAL should only always be used for subflows, and unless there's a good
|
requirement options are shown in the Admin Console when defining a flow. CONDITIONAL should only always be used for subflows, and unless there's a good
|
||||||
reason for doing otherwise, the requirement on a authenticator should be REQUIRED, ALTERNATIVE and DISABLED:
|
reason for doing otherwise, the requirement on a authenticator should be REQUIRED, ALTERNATIVE and DISABLED:
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
|
@ -730,7 +730,7 @@ If it returns true, then the flow manager will invoke Authenticator.setRequiredA
|
||||||
----
|
----
|
||||||
|
|
||||||
The next few methods define how the Authenticator can be configured.
|
The next few methods define how the Authenticator can be configured.
|
||||||
The isConfigurable() method is a flag which specifies to the admin console on whether the Authenticator can be configured within a flow.
|
The isConfigurable() method is a flag which specifies to the Admin Console on whether the Authenticator can be configured within a flow.
|
||||||
The getConfigProperties() method returns a list of ProviderConfigProperty objects.
|
The getConfigProperties() method returns a list of ProviderConfigProperty objects.
|
||||||
These objects define a specific configuration attribute.
|
These objects define a specific configuration attribute.
|
||||||
|
|
||||||
|
@ -757,18 +757,18 @@ These objects define a specific configuration attribute.
|
||||||
|
|
||||||
Each ProviderConfigProperty defines the name of the config property.
|
Each ProviderConfigProperty defines the name of the config property.
|
||||||
This is the key used in the config map stored in AuthenticatorConfigModel.
|
This is the key used in the config map stored in AuthenticatorConfigModel.
|
||||||
The label defines how the config option will be displayed in the admin console.
|
The label defines how the config option will be displayed in the Admin Console.
|
||||||
The type defines if it is a String, Boolean, or other type.
|
The type defines if it is a String, Boolean, or other type.
|
||||||
The admin console will display different UI inputs depending on the type.
|
The Admin Console will display different UI inputs depending on the type.
|
||||||
The help text is what will be shown in the tooltip for the config attribute in the admin console.
|
The help text is what will be shown in the tooltip for the config attribute in the Admin Console.
|
||||||
Read the javadoc of ProviderConfigProperty for more detail.
|
Read the javadoc of ProviderConfigProperty for more detail.
|
||||||
|
|
||||||
The rest of the methods are for the admin console.
|
The rest of the methods are for the Admin Console.
|
||||||
getHelpText() is the tooltip text that will be shown when you are picking the Authenticator you want to bind to an execution.
|
getHelpText() is the tooltip text that will be shown when you are picking the Authenticator you want to bind to an execution.
|
||||||
getDisplayType() is the text that will be shown in the admin console when listing the Authenticator.
|
getDisplayType() is the text that will be shown in the Admin Console when listing the Authenticator.
|
||||||
getReferenceCategory() is just a category the Authenticator belongs to.
|
getReferenceCategory() is just a category the Authenticator belongs to.
|
||||||
|
|
||||||
==== Adding Authenticator Form
|
==== Adding an authenticator form
|
||||||
|
|
||||||
{project_name} comes with a Freemarker <<_themes,theme and template engine>>.
|
{project_name} comes with a Freemarker <<_themes,theme and template engine>>.
|
||||||
The createForm() method you called within authenticate() of your Authenticator class, builds an HTML page from a file within your login theme: `secret-question.ftl`.
|
The createForm() method you called within authenticate() of your Authenticator class, builds an HTML page from a file within your login theme: `secret-question.ftl`.
|
||||||
|
@ -818,9 +818,9 @@ Importing this template, instead of the standard `template.ftl` allows {project_
|
||||||
a different credential or execution.
|
a different credential or execution.
|
||||||
|
|
||||||
[[_adding_authenticator]]
|
[[_adding_authenticator]]
|
||||||
==== Adding Authenticator to a Flow
|
==== Adding an authenticator to a flow
|
||||||
|
|
||||||
Adding an Authenticator to a flow must be done in the admin console.
|
Adding an Authenticator to a flow must be done in the Admin Console.
|
||||||
If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently defined flows.
|
If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently defined flows.
|
||||||
You cannot modify built in flows, so, to add the Authenticator we've created you have to copy an existing flow or create your own.
|
You cannot modify built in flows, so, to add the Authenticator we've created you have to copy an existing flow or create your own.
|
||||||
Our hope is that the user interface is sufficiently clear so that you can determine how to create a flow and add the Authenticator. For
|
Our hope is that the user interface is sufficiently clear so that you can determine how to create a flow and add the Authenticator. For
|
||||||
|
@ -829,13 +829,13 @@ more details, see the `Authentication Flows` chapter in link:{adminguide_link}[{
|
||||||
After you've created your flow, you have to bind it to the login action you want to bind it to.
|
After you've created your flow, you have to bind it to the login action you want to bind it to.
|
||||||
If you go to the Authentication menu and go to the Bindings tab you will see options to bind a flow to the browser, registration, or direct grant flow.
|
If you go to the Authentication menu and go to the Bindings tab you will see options to bind a flow to the browser, registration, or direct grant flow.
|
||||||
|
|
||||||
=== Required Action Walkthrough
|
=== Required action walkthrough
|
||||||
|
|
||||||
In this section we will discuss how to define a required action.
|
In this section we will discuss how to define a required action.
|
||||||
In the Authenticator section you may have wondered, "How will we get the user's answer to the secret question entered into the system?". As we showed in the example, if the answer is not set up, a required action will be triggered.
|
In the Authenticator section you may have wondered, "How will we get the user's answer to the secret question entered into the system?". As we showed in the example, if the answer is not set up, a required action will be triggered.
|
||||||
This section discusses how to implement the required action for the Secret Question Authenticator.
|
This section discusses how to implement the required action for the Secret Question Authenticator.
|
||||||
|
|
||||||
==== Packaging Classes and Deployment
|
==== Packaging classes and deployment
|
||||||
|
|
||||||
You will package your classes within a single jar.
|
You will package your classes within a single jar.
|
||||||
This jar does not have to be separate from other provider classes but it must contain a file named `org.keycloak.authentication.RequiredActionFactory` and must be contained in the `META-INF/services/` directory of your jar.
|
This jar does not have to be separate from other provider classes but it must contain a file named `org.keycloak.authentication.RequiredActionFactory` and must be contained in the `META-INF/services/` directory of your jar.
|
||||||
|
@ -927,17 +927,17 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
The getDisplayText() method is just for the admin console when it wants to display a friendly name for the required action.
|
The getDisplayText() method is just for the Admin Console when it wants to display a friendly name for the required action.
|
||||||
|
|
||||||
==== Enable Required Action
|
==== Enable required action
|
||||||
|
|
||||||
The final thing you have to do is go into the admin console.
|
The final thing you have to do is go into the Admin Console.
|
||||||
Click on the Authentication left menu.
|
Click on the Authentication left menu.
|
||||||
Click on the Required Actions tab.
|
Click on the Required Actions tab.
|
||||||
Click on the Register button and choose your new Required Action.
|
Click on the Register button and choose your new Required Action.
|
||||||
Your new required action should now be displayed and enabled in the required actions list.
|
Your new required action should now be displayed and enabled in the required actions list.
|
||||||
|
|
||||||
=== Modifying/Extending the Registration Form
|
=== Modifying or extending the registration form
|
||||||
|
|
||||||
It is entirely possible for you to implement your own flow with a set of Authenticators to totally change how registration is done in {project_name}.
|
It is entirely possible for you to implement your own flow with a set of Authenticators to totally change how registration is done in {project_name}.
|
||||||
But what you'll usually want to do is just add a little bit of validation to the out of the box registration page.
|
But what you'll usually want to do is just add a little bit of validation to the out of the box registration page.
|
||||||
|
@ -945,7 +945,7 @@ An additional SPI was created to be able to do this.
|
||||||
It basically allows you to add validation of form elements on the page as well as to initialize UserModel attributes and data after the user has been registered.
|
It basically allows you to add validation of form elements on the page as well as to initialize UserModel attributes and data after the user has been registered.
|
||||||
We'll look at both the implementation of the user profile registration processing as well as the registration Google Recaptcha plugin.
|
We'll look at both the implementation of the user profile registration processing as well as the registration Google Recaptcha plugin.
|
||||||
|
|
||||||
==== Implementation FormAction Interface
|
==== Implementation FormAction interface
|
||||||
|
|
||||||
The core interface you have to implement is the FormAction interface.
|
The core interface you have to implement is the FormAction interface.
|
||||||
A FormAction is responsible for rendering and processing a portion of the page.
|
A FormAction is responsible for rendering and processing a portion of the page.
|
||||||
|
@ -1025,8 +1025,7 @@ We then validate the recaptcha.
|
||||||
If successful, ValidationContext.success() is called.
|
If successful, ValidationContext.success() is called.
|
||||||
If not, we invoke ValidationContext.validationError() passing in the formData (so the user doesn't have to re-enter data), we also specify an error message we want displayed.
|
If not, we invoke ValidationContext.validationError() passing in the formData (so the user doesn't have to re-enter data), we also specify an error message we want displayed.
|
||||||
The error message must point to a message bundle property in the internationalized message bundles.
|
The error message must point to a message bundle property in the internationalized message bundles.
|
||||||
For other registration extensions validate() might be validating the format of a form element, i.e.
|
For other registration extensions validate() might be validating the format of a form element, for example an alternative email attribute.
|
||||||
an alternative email attribute.
|
|
||||||
|
|
||||||
Let's also look at the user profile plugin that is used to validate email address and other user information when registering.
|
Let's also look at the user profile plugin that is used to validate email address and other user information when registering.
|
||||||
|
|
||||||
|
@ -1106,7 +1105,7 @@ The appropriate methods are called to initialize UserModel data.
|
||||||
Finally, you are also required to define a FormActionFactory class.
|
Finally, you are also required to define a FormActionFactory class.
|
||||||
This class is implemented similarly to AuthenticatorFactory, so we won't go over it.
|
This class is implemented similarly to AuthenticatorFactory, so we won't go over it.
|
||||||
|
|
||||||
==== Packaging the Action
|
==== Packaging the action
|
||||||
|
|
||||||
You will package your classes within a single jar.
|
You will package your classes within a single jar.
|
||||||
This jar must contain a file named `org.keycloak.authentication.FormActionFactory` and must be contained in the `META-INF/services/` directory of your jar.
|
This jar must contain a file named `org.keycloak.authentication.FormActionFactory` and must be contained in the `META-INF/services/` directory of your jar.
|
||||||
|
@ -1124,9 +1123,9 @@ This services/ file is used by {project_name} to scan the providers it has to lo
|
||||||
|
|
||||||
To deploy this jar, just copy it to the `standalone/deployments` directory.
|
To deploy this jar, just copy it to the `standalone/deployments` directory.
|
||||||
|
|
||||||
==== Adding FormAction to the Registration Flow
|
==== Adding FormAction to the registration flow
|
||||||
|
|
||||||
Adding a FormAction to a registration page flow must be done in the admin console.
|
Adding a FormAction to a registration page flow must be done in the Admin Console.
|
||||||
If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently defined flows.
|
If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently defined flows.
|
||||||
You cannot modify built in flows, so, to add the Authenticator we've created you have to copy an existing flow or create your own.
|
You cannot modify built in flows, so, to add the Authenticator we've created you have to copy an existing flow or create your own.
|
||||||
I'm hoping the UI is intuitive enough so that you can figure out for yourself how to create a flow and add the FormAction.
|
I'm hoping the UI is intuitive enough so that you can figure out for yourself how to create a flow and add the FormAction.
|
||||||
|
@ -1139,10 +1138,10 @@ Make sure your FormAction comes after "Registration User Creation" by using the
|
||||||
After you've created your flow, you have to bind it to registration.
|
After you've created your flow, you have to bind it to registration.
|
||||||
If you go to the Authentication menu and go to the Bindings tab you will see options to bind a flow to the browser, registration, or direct grant flow.
|
If you go to the Authentication menu and go to the Bindings tab you will see options to bind a flow to the browser, registration, or direct grant flow.
|
||||||
|
|
||||||
=== Modifying Forgot Password/Credential Flow
|
=== Modifying forgot password/credential flow
|
||||||
|
|
||||||
{project_name} also has a specific authentication flow for forgot password, or rather credential reset initiated by a user.
|
{project_name} also has a specific authentication flow for forgot password, or rather credential reset initiated by a user.
|
||||||
If you go to the admin console flows page, there is a "reset credentials" flow.
|
If you go to the Admin Console flows page, there is a "reset credentials" flow.
|
||||||
By default, {project_name} asks for the email or username of the user and sends an email to them.
|
By default, {project_name} asks for the email or username of the user and sends an email to them.
|
||||||
If the user clicks on the link, then they are able to reset both their password and OTP (if an OTP has been set up). You can disable automatic OTP reset by disabling the "Reset OTP" authenticator in the flow.
|
If the user clicks on the link, then they are able to reset both their password and OTP (if an OTP has been set up). You can disable automatic OTP reset by disabling the "Reset OTP" authenticator in the flow.
|
||||||
|
|
||||||
|
@ -1160,11 +1159,14 @@ So, if AuthenticationFlowContext.getUser() returns null, you should proceed with
|
||||||
I suggest that if you want to add secret questions to this flow, you should ask these questions after the email is sent.
|
I suggest that if you want to add secret questions to this flow, you should ask these questions after the email is sent.
|
||||||
In other words, add your custom authenticator after the "Send Reset Email" authenticator.
|
In other words, add your custom authenticator after the "Send Reset Email" authenticator.
|
||||||
|
|
||||||
=== Modifying First Broker Login Flow
|
=== Modifying first broker login flow
|
||||||
|
|
||||||
First Broker Login flow is used during first login with some identity provider.
|
First Broker Login flow is used during first login with some identity provider.
|
||||||
Term `First Login` means that there is not yet existing {project_name} account linked with the particular authenticated identity provider account.
|
Term `First Login` means that there is not yet existing {project_name} account linked with the particular authenticated identity provider account.
|
||||||
For more details about this flow see the `Identity Brokering` chapter in link:{adminguide_link}[{adminguide_name}] .
|
|
||||||
|
[role="_additional-resource"]
|
||||||
|
.Additional resources
|
||||||
|
* See the `Identity Brokering` chapter in link:{adminguide_link}[{adminguide_name}] .
|
||||||
|
|
||||||
[[_client_authentication]]
|
[[_client_authentication]]
|
||||||
=== Authentication of clients
|
=== Authentication of clients
|
||||||
|
@ -1175,7 +1177,9 @@ to the {project_name} server (like the request for exchange code to access token
|
||||||
But the client authentication can be also used directly by you during `Direct Access grants` (represented by OAuth2 `Resource Owner Password Credentials Flow`)
|
But the client authentication can be also used directly by you during `Direct Access grants` (represented by OAuth2 `Resource Owner Password Credentials Flow`)
|
||||||
or during `Service account` authentication (represented by OAuth2 `Client Credentials Flow`).
|
or during `Service account` authentication (represented by OAuth2 `Client Credentials Flow`).
|
||||||
|
|
||||||
For more details about {project_name} adapter and OAuth2 flows see link:{adapterguide_link}[{adapterguide_name}].
|
[role="_additional-resource"]
|
||||||
|
.Additional resources
|
||||||
|
* For more details about {project_name} adapter and OAuth2 flows see link:{adapterguide_link}[{adapterguide_name}].
|
||||||
|
|
||||||
==== Default implementations
|
==== Default implementations
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,25 @@
|
||||||
[[_custom_user_attributes]]
|
[[_custom_user_attributes]]
|
||||||
== Custom User Attributes
|
== Custom user attributes
|
||||||
|
|
||||||
You can add custom user attributes to the registration page and account management console with a custom theme. This chapter describes how to add attributes
|
You can add custom user attributes to the registration page and account management console with a custom theme.
|
||||||
to a custom theme, but you should refer to the <<_themes,Themes>> chapter on how to create a custom theme.
|
|
||||||
|
|
||||||
=== Registration Page
|
[role="_additional-resources"]
|
||||||
|
.Additional resources
|
||||||
|
|
||||||
To be able to enter custom attributes in the registration page copy the template `themes/base/login/register.ftl` to the login type of your custom theme. Then
|
* See <<_themes,Themes>> for how to create a custom theme.
|
||||||
open the copy in an editor.
|
|
||||||
|
|
||||||
As an example to add a mobile number to the registration page add the following snippet to the form:
|
=== Registration page
|
||||||
|
|
||||||
|
Use this procedure to enter custom attributes in the registration page.
|
||||||
|
|
||||||
|
.Procedure
|
||||||
|
|
||||||
|
. copy the template `themes/base/login/register.ftl` to the login type of your custom theme.
|
||||||
|
|
||||||
|
. open the copy in an editor.
|
||||||
|
+
|
||||||
|
For example, to add a mobile number to the registration page, add the following snippet to the form:
|
||||||
|
+
|
||||||
[source,html]
|
[source,html]
|
||||||
----
|
----
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -24,17 +33,22 @@ As an example to add a mobile number to the registration page add the following
|
||||||
</div>
|
</div>
|
||||||
----
|
----
|
||||||
|
|
||||||
Ensure the name of the input html element starts with `user.attributes.`. In the example above, the attribute will be stored by Keycloak with the name `mobile`.
|
. Ensure the name of the input html element starts with `user.attributes.`. In the example above, the attribute will be stored by {project_name} with the name `mobile`.
|
||||||
|
|
||||||
To see the changes make sure your realm is using your custom theme for the login theme and open the registration page.
|
. To see the changes, make sure your realm is using your custom theme for the login theme and open the registration page.
|
||||||
|
|
||||||
=== Account Management Console
|
=== Account Management Console
|
||||||
|
|
||||||
To be able to manage custom attributes in the user profile page in the account management console copy the template `themes/base/account/account.ftl` to the
|
Use this procedure to manage custom attributes in the user profile page in the account management console.
|
||||||
account type of your custom theme. Then open the copy in an editor.
|
|
||||||
|
|
||||||
|
.Procedure
|
||||||
|
. copy the template `themes/base/account/account.ftl` to the
|
||||||
|
account type of your custom theme.
|
||||||
|
|
||||||
|
. Open the copy in an editor.
|
||||||
|
+
|
||||||
As an example to add a mobile number to the account page add the following snippet to the form:
|
As an example to add a mobile number to the account page add the following snippet to the form:
|
||||||
|
+
|
||||||
[source,html]
|
[source,html]
|
||||||
----
|
----
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -48,6 +62,6 @@ As an example to add a mobile number to the account page add the following snipp
|
||||||
</div>
|
</div>
|
||||||
----
|
----
|
||||||
|
|
||||||
Ensure the name of the input html element starts with `user.attributes.`.
|
. Ensure the name of the input html element starts with `user.attributes.`.
|
||||||
|
|
||||||
To see the changes make sure your realm is using your custom theme for the account theme and open the user profile page in the account management console.
|
. To see the changes, make sure your realm is using your custom theme for the account theme and open the user profile page in the account management console.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[[_extensions]]
|
[[_extensions]]
|
||||||
|
|
||||||
== Extending the Server
|
== Extending the server
|
||||||
|
|
||||||
The {project_name} SPI framework offers the possibility to implement or override particular built-in providers. However {project_name}
|
The {project_name} SPI framework offers the possibility to implement or override particular built-in providers. However {project_name}
|
||||||
also provides capabilities to extend its core functionalities and domain. This includes possibilities to:
|
also provides capabilities to extend its core functionalities and domain. This includes possibilities to:
|
||||||
|
@ -24,19 +24,22 @@ Object getResource();
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
which allows you to return an object, which acts as a https://github.com/jax-rs[JAX-RS Resource]. For more details, see the Javadoc and our examples.
|
Use this method to return an object, which acts as a https://github.com/jax-rs[JAX-RS Resource]. For more details, see the Javadoc and our examples.
|
||||||
There is a very simple example in the example distribution in `providers/rest` and there is a more advanced example in `providers/domain-extension`,
|
There is a very simple example in the example distribution in `providers/rest` and there is a more advanced example in `providers/domain-extension`,
|
||||||
which shows how to add an authenticated REST endpoint and other functionalities like <<_extensions_spi, Adding your own SPI>>
|
which shows how to add an authenticated REST endpoint and other functionalities like <<_extensions_spi, Adding your own SPI>>
|
||||||
or <<_extensions_jpa,Extending datamodel with your own JPA entities>>.
|
or <<_extensions_jpa,Extending the datamodel with custom JPA entities>>.
|
||||||
|
|
||||||
For details on how to package and deploy a custom provider, refer to the <<_providers,Service Provider Interfaces>> chapter.
|
For details on how to package and deploy a custom provider, refer to the <<_providers,Service Provider Interfaces>> chapter.
|
||||||
|
|
||||||
[[_extensions_spi]]
|
[[_extensions_spi]]
|
||||||
=== Add your own custom SPI
|
=== Add your own custom SPI
|
||||||
|
|
||||||
This is useful especially with the <<_extensions_rest,Custom REST endpoints>>. To add your own kind of SPI, you need to
|
A custom SPI is especially useful with Custom REST endpoints. Use this procedure to add your own SPI
|
||||||
implement the interface `org.keycloak.provider.Spi` and define the ID of your SPI and the `ProviderFactory` and `Provider` classes. That looks like this:
|
|
||||||
|
|
||||||
|
.Procedure
|
||||||
|
|
||||||
|
. implement the interface `org.keycloak.provider.Spi` and define the ID of your SPI and the `ProviderFactory` and `Provider` classes. That looks like this:
|
||||||
|
+
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
package org.keycloak.examples.domainextension.spi;
|
package org.keycloak.examples.domainextension.spi;
|
||||||
|
@ -70,21 +73,25 @@ public class ExampleSpi implements Spi {
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
Then you need to create the file `META-INF/services/org.keycloak.provider.Spi` and add the class of your SPI to it. For example:
|
. Create the file `META-INF/services/org.keycloak.provider.Spi` and add the class of your SPI to it. For example:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
org.keycloak.examples.domainextension.spi.ExampleSpi
|
org.keycloak.examples.domainextension.spi.ExampleSpi
|
||||||
----
|
----
|
||||||
|
|
||||||
The next step is to create the interfaces `ExampleServiceProviderFactory`, which extends from `ProviderFactory` and `ExampleService`, which extends from `Provider`.
|
. Create the interfaces `ExampleServiceProviderFactory`, which extends from `ProviderFactory` and `ExampleService`, which extends from `Provider`.
|
||||||
The `ExampleService` will usually contain the business methods you need for your use case. Note that the `ExampleServiceProviderFactory` instance
|
The `ExampleService` will usually contain the business methods you need for your use case. Note that the `ExampleServiceProviderFactory` instance
|
||||||
is always scoped per application, however `ExampleService` is scoped per-request (or more accurately per `KeycloakSession` lifecycle).
|
is always scoped per application, however `ExampleService` is scoped per-request (or more accurately per `KeycloakSession` lifecycle).
|
||||||
|
|
||||||
Finally you need to implement your providers in the same manner as described in the <<_providers,Service Provider Interfaces>> chapter.
|
. Finally you need to implement your providers in the same manner as described in the <<_providers,Service Provider Interfaces>> chapter.
|
||||||
|
|
||||||
For more details, take a look at the example distribution at `providers/domain-extension`, which shows an Example SPI similar to the one above.
|
For more details, take a look at the example distribution at `providers/domain-extension`, which shows an Example SPI similar to the one above.
|
||||||
|
|
||||||
|
[role="_additional-resources"]
|
||||||
|
.Additional resources
|
||||||
|
* <<_extensions_rest,Custom REST endpoints>>
|
||||||
|
|
||||||
[[_extensions_jpa]]
|
[[_extensions_jpa]]
|
||||||
=== Add custom JPA entities to the {project_name} data model
|
=== Add custom JPA entities to the {project_name} data model
|
||||||
|
|
||||||
|
@ -146,4 +153,3 @@ The DB schema will be automatically updated at startup.
|
||||||
For more details, take a look at the example distribution at example `providers/domain-extension`, which shows the `ExampleJpaEntityProvider` and `example-changelog.xml` described above.
|
For more details, take a look at the example distribution at example `providers/domain-extension`, which shows the `ExampleJpaEntityProvider` and `example-changelog.xml` described above.
|
||||||
|
|
||||||
NOTE: Don't forget to always backup your database before doing any changes in the Liquibase changelog and triggering a DB update.
|
NOTE: Don't forget to always backup your database before doing any changes in the Liquibase changelog and triggering a DB update.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
== Identity Brokering APIs
|
== Identity Brokering APIs
|
||||||
|
|
||||||
{project_name} can delegate authentication to a parent IDP for login. A typical example of this is the case
|
{project_name} can delegate authentication to a parent IDP for login. A typical example of this is the case
|
||||||
where you want users to be able to login through a social provider like Facebook or Google. {project_name}
|
where you want users to be able to login through a social provider such as Facebook or Google. You can
|
||||||
also allows you to link existing accounts to a brokered IDP. This section talks about some APIs that your applications
|
also link existing accounts to a brokered IDP. This section describes some APIs that your applications
|
||||||
can use as it pertains to identity brokering.
|
can use as it pertains to identity brokering.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Client Initiated Account Linking
|
=== Client initiated account linking
|
||||||
|
|
||||||
Some applications want to integrate with social providers like Facebook, but do not want to provide an option to login via
|
Some applications want to integrate with social providers like Facebook, but do not want to provide an option to login via
|
||||||
these social providers. {project_name} offers a browser-based API that applications can use to link an existing
|
these social providers. {project_name} offers a browser-based API that applications can use to link an existing
|
||||||
|
@ -88,7 +88,7 @@ to the application. If there is an error condition and the auth server deems it
|
||||||
While this API guarantees that the application initiated the request, it does not completely prevent CSRF attacks for this operation. The application
|
While this API guarantees that the application initiated the request, it does not completely prevent CSRF attacks for this operation. The application
|
||||||
is still responsible for guarding against CSRF attacks target at itself.
|
is still responsible for guarding against CSRF attacks target at itself.
|
||||||
|
|
||||||
==== Refreshing External Tokens
|
==== Refreshing external tokens
|
||||||
|
|
||||||
If you are using the external token generated by logging into the provider (i.e. a Facebook or GitHub token), you can refresh this token by re-initiating the account linking API.
|
If you are using the external token generated by logging into the provider (i.e. a Facebook or GitHub token), you can refresh this token by re-initiating the account linking API.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Retrieving External IDP Tokens
|
=== Retrieving external IDP tokens
|
||||||
|
|
||||||
{project_name} allows you to store tokens and responses from the authentication process with the external IDP.
|
{project_name} allows you to store tokens and responses from the authentication process with the external IDP.
|
||||||
For that, you can use the `Store Token` configuration option on the IDP's settings page.
|
For that, you can use the `Store Token` configuration option on the IDP's settings page.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[[_locale_selector]]
|
[[_locale_selector]]
|
||||||
=== Locale Selector
|
=== Locale selector
|
||||||
|
|
||||||
By default, the locale is selected using the `DefaultLocaleSelectorProvider` which implements the `LocaleSelectorProvider` interface. English is the default language when internationalization is disabled.
|
By default, the locale is selected using the `DefaultLocaleSelectorProvider` which implements the `LocaleSelectorProvider` interface. English is the default language when internationalization is disabled.
|
||||||
With internationalization enabled, the locale is resolved according to the logic described in the link:{adminguide_link}#_user_locale_selection[{adminguide_name}].
|
With internationalization enabled, the locale is resolved according to the logic described in the link:{adminguide_link}#_user_locale_selection[{adminguide_name}].
|
||||||
|
@ -13,6 +13,10 @@ This behavior can be changed through the `LocaleSelectorSPI` by implementing the
|
||||||
|
|
||||||
The `LocaleSelectorProvider` interface has a single method, `resolveLocale`, which must return a locale given a `RealmModel` and a nullable `UserModel`. The actual request is available from the `KeycloakSession#getContext` method.
|
The `LocaleSelectorProvider` interface has a single method, `resolveLocale`, which must return a locale given a `RealmModel` and a nullable `UserModel`. The actual request is available from the `KeycloakSession#getContext` method.
|
||||||
|
|
||||||
Custom implementations can extend the `DefaultLocaleSelectorProvider` in order to reuse parts of the default behaviour. For example to ignore the `Accept-Language` request header, a custom implementation could extend the default provider, override it's `getAcceptLanguageHeaderLocale`, and return a null value. As a result the locale selection will fall back on the realms's default language.
|
Custom implementations can extend the `DefaultLocaleSelectorProvider` in order to reuse parts of the default behavior. For example to ignore the `Accept-Language` request header, a custom implementation could extend the default provider, override it's `getAcceptLanguageHeaderLocale`, and return a null value. As a result the locale selection will fall back on the realms's default language.
|
||||||
|
|
||||||
|
[role="_additional-resources"]
|
||||||
|
.Additional resources
|
||||||
|
|
||||||
|
* Follow the steps in <<_providers,Service Provider Interfaces>> for more details on how to create and deploy a custom provider.
|
||||||
|
|
||||||
Follow the steps in <<_providers,Service Provider Interfaces>> for more details on how to create and deploy a custom provider.
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class MyThemeSelectorProviderFactory implements ThemeSelectorProviderFact
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
NOTE: Keycloak creates a single instance of provider factories which makes it possible to store state for multiple requests.
|
NOTE: {project_name} creates a single instance of provider factories which makes it possible to store state for multiple requests.
|
||||||
Provider instances are created by calling create on the factory for each request so these should be light-weight object.
|
Provider instances are created by calling create on the factory for each request so these should be light-weight object.
|
||||||
|
|
||||||
Example ThemeSelectorProvider:
|
Example ThemeSelectorProvider:
|
||||||
|
@ -126,11 +126,11 @@ public class MyThemeSelectorProvider implements ThemeSelectorProvider {
|
||||||
----
|
----
|
||||||
|
|
||||||
[[_providers_admin_console]]
|
[[_providers_admin_console]]
|
||||||
==== Show info from your SPI implementation in admin console
|
==== Show info from your SPI implementation in the Admin Console
|
||||||
|
|
||||||
Sometimes it is useful to show additional info about your Provider to a {project_name} administrator. You can show provider build time information (eg. version of
|
Sometimes it is useful to show additional info about your Provider to a {project_name} administrator. You can show provider build time information (for example, version of
|
||||||
custom provider currently installed), current configuration of the provider (eg. url of remote system your provider talks to) or some operational info
|
custom provider currently installed), current configuration of the provider (eg. url of remote system your provider talks to) or some operational info
|
||||||
(average time of response from remote system your provider talks to). {project_name} admin console provides Server Info page to show this kind of information.
|
(average time of response from remote system your provider talks to). {project_name} Admin Console provides Server Info page to show this kind of information.
|
||||||
|
|
||||||
To show info from your provider it is enough to implement `org.keycloak.provider.ServerInfoAwareProviderFactory` interface in your `ProviderFactory`.
|
To show info from your provider it is enough to implement `org.keycloak.provider.ServerInfoAwareProviderFactory` interface in your `ProviderFactory`.
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ The alternative approach is to deploy as a module.
|
||||||
|
|
||||||
If you are creating a custom SPI you will need to deploy it as a module, otherwise we recommend using the {project_name} deployer approach.
|
If you are creating a custom SPI you will need to deploy it as a module, otherwise we recommend using the {project_name} deployer approach.
|
||||||
|
|
||||||
==== Using the {project_name} Deployer
|
==== Using the {project_name} deployer
|
||||||
|
|
||||||
If you copy your provider jar to the {project_name} `standalone/deployments/` directory, your provider will automatically be deployed.
|
If you copy your provider jar to the {project_name} `standalone/deployments/` directory, your provider will automatically be deployed.
|
||||||
Hot deployment works too. Additionally, your provider jar works similarly to other components deployed in a {appserver_name}
|
Hot deployment works too. Additionally, your provider jar works similarly to other components deployed in a {appserver_name}
|
||||||
|
@ -206,20 +206,22 @@ it really easy to use third party jars as you can just put these libraries in th
|
||||||
|
|
||||||
==== Register a provider using Modules
|
==== Register a provider using Modules
|
||||||
|
|
||||||
To register a provider using Modules first create a module.
|
.Procedure
|
||||||
To do this you can either use the jboss-cli script or manually create a folder inside `KEYCLOAK_HOME/modules` and add your jar and a `module.xml`.
|
. To register a provider using Modules, create a module.
|
||||||
|
+
|
||||||
|
You can either use the jboss-cli script or manually create a folder inside `KEYCLOAK_HOME/modules` and add your jar and a `module.xml`.
|
||||||
|
+
|
||||||
For example to add the event listener sysout example provider using the `jboss-cli` script execute:
|
For example to add the event listener sysout example provider using the `jboss-cli` script execute:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.acme.provider --resources=target/provider.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.acme.provider --resources=target/provider.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
||||||
----
|
----
|
||||||
Or to manually create it start by creating the folder `KEYCLOAK_HOME/modules/org/acme/provider/main`.
|
|
||||||
Then copy `provider.jar` to this folder and create `module.xml` with the following content:
|
|
||||||
|
|
||||||
|
. To manually create the module, create the folder `KEYCLOAK_HOME/modules/org/acme/provider/main`. Then copy `provider.jar` to this folder and create `module.xml` with the following content:
|
||||||
|
+
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module xmlns="urn:jboss:module:1.3" name="org.acme.provider">
|
<module xmlns="urn:jboss:module:1.3" name="org.acme.provider">
|
||||||
<resources>
|
<resources>
|
||||||
|
@ -232,10 +234,9 @@ Then copy `provider.jar` to this folder and create `module.xml` with the followi
|
||||||
</module>
|
</module>
|
||||||
----
|
----
|
||||||
|
|
||||||
Once you've created the module you need to register this module with {project_name}.
|
. Register this module with {project_name} by editing the keycloak-server subsystem section of
|
||||||
This is done by editing the keycloak-server subsystem section of
|
|
||||||
`standalone.xml`, `standalone-ha.xml`, or `domain.xml`, and adding it to the providers:
|
`standalone.xml`, `standalone-ha.xml`, or `domain.xml`, and adding it to the providers:
|
||||||
|
+
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
|
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
|
||||||
|
@ -306,13 +307,13 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
You have to define the `@Local` annotation and specify your provider class there. If you don't do this, EJB will
|
You define the `@Local` annotation and specify your provider class there. If you don't do this, EJB will
|
||||||
not proxy the provider instance correctly and your provider won't work.
|
not proxy the provider instance correctly and your provider won't work.
|
||||||
|
|
||||||
You must put the `@Remove` annotation on the `close()` method of your provider. If you don't, the stateful bean
|
You put the `@Remove` annotation on the `close()` method of your provider. If you don't, the stateful bean
|
||||||
will never be cleaned up and you may eventually see error messages.
|
will never be cleaned up and you may eventually see error messages.
|
||||||
|
|
||||||
Implementations of `ProviderFactory` are required to be plain java objects. Your factory class would
|
Ixmplementations of `ProviderFactory` are required to be plain java objects. Your factory class would
|
||||||
perform a JNDI lookup of the Stateful EJB in its `create()` method.
|
perform a JNDI lookup of the Stateful EJB in its `create()` method.
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
|
@ -336,7 +337,7 @@ public class EjbExampleUserStorageProviderFactory
|
||||||
----
|
----
|
||||||
|
|
||||||
[[_script_providers]]
|
[[_script_providers]]
|
||||||
=== JavaScript Providers
|
=== JavaScript providers
|
||||||
|
|
||||||
{project_name} has the ability to execute scripts during runtime in order to allow administrators to customize specific functionalities:
|
{project_name} has the ability to execute scripts during runtime in order to allow administrators to customize specific functionalities:
|
||||||
|
|
||||||
|
@ -448,7 +449,7 @@ For JavaScript Policies when using {project_name} Authorization Services. You ca
|
||||||
+
|
+
|
||||||
For OpenID Connect Script Protocol Mappers. You can have one or multiple mappers in the same JAR file
|
For OpenID Connect Script Protocol Mappers. You can have one or multiple mappers in the same JAR file
|
||||||
|
|
||||||
For each script file in your `JAR` file you must have a corresponding entry in `META-INF/keycloak-scripts.json` that maps your scripts files to a specific provider type. For that you should provide the following properties for each entry:
|
For each script file in your `JAR` file, you need a corresponding entry in `META-INF/keycloak-scripts.json` that maps your scripts files to a specific provider type. For that you should provide the following properties for each entry:
|
||||||
|
|
||||||
* `name`
|
* `name`
|
||||||
+
|
+
|
||||||
|
@ -463,13 +464,13 @@ An optional text that better describes the intend of the script file
|
||||||
+
|
+
|
||||||
The name of the script file. This property is *mandatory* and should map to a file within the JAR.
|
The name of the script file. This property is *mandatory* and should map to a file within the JAR.
|
||||||
|
|
||||||
==== Deploy the Script JAR
|
==== Deploy the script JAR
|
||||||
|
|
||||||
Once you have a JAR file with a descriptor and the scripts you want to deploy, you just need to copy the JAR to the {project_name} `standalone/deployments/` directory.
|
Once you have a JAR file with a descriptor and the scripts you want to deploy, you just need to copy the JAR to the {project_name} `standalone/deployments/` directory.
|
||||||
|
|
||||||
==== Using {project_name} Administration Console to upload scripts
|
==== Using the {project_name} Admin Console to upload scripts
|
||||||
|
|
||||||
NOTE: Ability to upload scripts through the admin console is deprecated and will be removed in a future version of {project_name}
|
NOTE: Ability to upload scripts through the Admin Console is deprecated and will be removed in a future version of {project_name}.
|
||||||
|
|
||||||
Administrators cannot upload scripts to the server. This behavior prevents potential harm to the system in case
|
Administrators cannot upload scripts to the server. This behavior prevents potential harm to the system in case
|
||||||
malicious scripts are accidentally executed. Administrators should always deploy scripts directly to the server using a
|
malicious scripts are accidentally executed. Administrators should always deploy scripts directly to the server using a
|
||||||
|
@ -482,4 +483,4 @@ For more details about how to enable the `upload_scripts` feature. Please, take
|
||||||
|
|
||||||
=== Available SPIs
|
=== Available SPIs
|
||||||
|
|
||||||
If you want to see list of all available SPIs at runtime, you can check `Server Info` page in admin console as described in <<_providers_admin_console,Admin Console>> section.
|
If you want to see list of all available SPIs at runtime, you can check `Server Info` page in Admin Console as described in <<_providers_admin_console,Admin Console>> section.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[[_saml_role_mappings_spi]]
|
[[_saml_role_mappings_spi]]
|
||||||
== SAML Role Mappings SPI
|
== SAML role mappings SPI
|
||||||
|
|
||||||
{project_name} defines a SPI for mapping SAML roles into roles that exist in the SP environment. The roles returned by
|
{project_name} defines a SPI for mapping SAML roles into roles that exist in the SP environment. The roles returned by
|
||||||
a third-party IDP might not always correspond to the roles that were defined for the SP application so there is a need for a
|
a third-party IDP might not always correspond to the roles that were defined for the SP application so there is a need for a
|
||||||
|
@ -13,7 +13,7 @@ roles assigned to the SAML principal) depending on the use case.
|
||||||
For details about the configuration of the role mappings provider for the SAML adapter as well as a description of the default
|
For details about the configuration of the role mappings provider for the SAML adapter as well as a description of the default
|
||||||
implementations available see the link:{adapterguide_link}[{adapterguide_name}].
|
implementations available see the link:{adapterguide_link}[{adapterguide_name}].
|
||||||
|
|
||||||
=== Implementing a Custom Role Mappings Provider
|
=== Implementing a custom role mappings provider
|
||||||
|
|
||||||
To implement a custom role mappings provider one first needs to implement the `org.keycloak.adapters.saml.RoleMappingsProvider`
|
To implement a custom role mappings provider one first needs to implement the `org.keycloak.adapters.saml.RoleMappingsProvider`
|
||||||
interface. Then, a `META-INF/services/org.keycloak.adapters.saml.RoleMappingsProvider` file containing the fully qualified name
|
interface. Then, a `META-INF/services/org.keycloak.adapters.saml.RoleMappingsProvider` file containing the fully qualified name
|
||||||
|
|
|
@ -14,5 +14,7 @@ If you want a more flexible way to load templates and resources that can be achi
|
||||||
By implementing `ThemeResourceProviderFactory` and `ThemeResourceProvider` you can decide exactly how to load templates
|
By implementing `ThemeResourceProviderFactory` and `ThemeResourceProvider` you can decide exactly how to load templates
|
||||||
and resources.
|
and resources.
|
||||||
|
|
||||||
Follow the steps in <<_providers,Service Provider Interfaces>> for more details on how to create and deploy a custom
|
[role="_additional-resources"]
|
||||||
provider.
|
.Additional resources
|
||||||
|
* Follow the steps in <<_providers,Service Provider Interfaces>> for more details on how to create and deploy a custom provider.
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,7 @@ header, for example.
|
||||||
|
|
||||||
To create a custom theme selector you need to implement `ThemeSelectorProviderFactory` and `ThemeSelectorProvider`.
|
To create a custom theme selector you need to implement `ThemeSelectorProviderFactory` and `ThemeSelectorProvider`.
|
||||||
|
|
||||||
Follow the steps in <<_providers,Service Provider Interfaces>> for more details on how to create and deploy a custom
|
[role="_additional-resources"]
|
||||||
provider.
|
.Additional resources
|
||||||
|
* Follow the steps in <<_providers,Service Provider Interfaces>> for more details on how to create and deploy a custom provider.
|
||||||
|
|
||||||
|
|
|
@ -6,28 +6,35 @@ integrated with your applications.
|
||||||
|
|
||||||
image::images/login-sunrise.png[caption="",title="Login page with sunrise example theme"]
|
image::images/login-sunrise.png[caption="",title="Login page with sunrise example theme"]
|
||||||
|
|
||||||
=== Theme Types
|
=== Theme types
|
||||||
|
|
||||||
A theme can provide one or more types to customize different aspects of {project_name}. The types available are:
|
A theme can provide one or more types to customize different aspects of {project_name}. The types available are:
|
||||||
|
|
||||||
* Account - Account management
|
* Account - Account management
|
||||||
* Admin - Admin console
|
* Admin - Admin Console
|
||||||
* Email - Emails
|
* Email - Emails
|
||||||
* Login - Login forms
|
* Login - Login forms
|
||||||
* Welcome - Welcome page
|
* Welcome - Welcome page
|
||||||
|
|
||||||
=== Configure Theme
|
=== Configuring a theme
|
||||||
|
|
||||||
All theme types, except welcome, are configured through the `Admin Console`. To change the theme used for a realm open the `Admin Console`, select
|
All theme types, except welcome, are configured through the Admin Console.
|
||||||
your realm from the drop-down box in the top left corner. Under `Realm Settings` click `Themes`.
|
|
||||||
|
|
||||||
NOTE: To set the theme for the `master` admin console you need to set the admin console theme for the `master` realm. To see the changes to the admin console
|
.Procedure
|
||||||
refresh the page.
|
|
||||||
|
|
||||||
To change the welcome theme you need to edit `standalone.xml`, `standalone-ha.xml`, or `domain.xml`.
|
. Log into the Admin Console.
|
||||||
|
. Select your realm from the drop-down box in the top left corner.
|
||||||
|
. Click *Realm Settings* from the menu.
|
||||||
|
. Click the *Themes* tab.
|
||||||
|
+
|
||||||
|
NOTE: To set the theme for the `master` Admin Console you need to set the Admin Console theme for the `master` realm.
|
||||||
|
+
|
||||||
|
. To see the changes to the Admin Console refresh the page.
|
||||||
|
|
||||||
Add `welcomeTheme` to the theme element, for example:
|
. Change the welcome theme by editing `standalone.xml`, `standalone-ha.xml`, or `domain.xml`.
|
||||||
|
|
||||||
|
. Add `welcomeTheme` to the theme element, for example:
|
||||||
|
+
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
<theme>
|
<theme>
|
||||||
|
@ -36,15 +43,14 @@ Add `welcomeTheme` to the theme element, for example:
|
||||||
...
|
...
|
||||||
</theme>
|
</theme>
|
||||||
----
|
----
|
||||||
|
. Restart the server for the changes to the welcome theme to take effect.
|
||||||
|
|
||||||
If the server is running you need to restart the server for the changes to the welcome theme to take effect.
|
=== Default themes
|
||||||
|
|
||||||
=== Default Themes
|
|
||||||
|
|
||||||
{project_name} comes bundled with default themes in the server's root `themes` directory. To simplify upgrading you should not edit the bundled themes
|
{project_name} comes bundled with default themes in the server's root `themes` directory. To simplify upgrading you should not edit the bundled themes
|
||||||
directly. Instead create your own theme that extends one of the bundled themes.
|
directly. Instead create your own theme that extends one of the bundled themes.
|
||||||
|
|
||||||
=== Creating a Theme
|
=== Creating a theme
|
||||||
|
|
||||||
A theme consists of:
|
A theme consists of:
|
||||||
|
|
||||||
|
@ -63,9 +69,15 @@ When extending a theme you can override individual resources (templates, stylesh
|
||||||
need to update your custom template when upgrading to a new release.
|
need to update your custom template when upgrading to a new release.
|
||||||
|
|
||||||
While creating a theme it's a good idea to disable caching as this makes it possible to edit theme resources directly from the `themes` directory without
|
While creating a theme it's a good idea to disable caching as this makes it possible to edit theme resources directly from the `themes` directory without
|
||||||
restarting {project_name}. To do this edit `standalone.xml`. For `theme` set `staticMaxAge` to `-1` and both
|
restarting {project_name}.
|
||||||
`cacheTemplates` and `cacheThemes` to `false`:
|
|
||||||
|
|
||||||
|
.Procedure
|
||||||
|
|
||||||
|
. Edit `standalone.xml`.
|
||||||
|
|
||||||
|
. For `theme` set `staticMaxAge` to `-1` and both
|
||||||
|
`cacheTemplates` and `cacheThemes` to `false`:
|
||||||
|
+
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
<theme>
|
<theme>
|
||||||
|
@ -76,31 +88,44 @@ restarting {project_name}. To do this edit `standalone.xml`. For `theme` set `st
|
||||||
</theme>
|
</theme>
|
||||||
----
|
----
|
||||||
|
|
||||||
Remember to re-enable caching in production as it will significantly impact performance.
|
. Create a directory in the `themes` directory.
|
||||||
|
+
|
||||||
To create a new theme start by creating a new directory in the `themes` directory. The name of the directory becomes the name of the theme. For example to
|
The name of the directory becomes the name of the theme. For example to
|
||||||
create a theme called `mytheme` create the directory `themes/mytheme`.
|
create a theme called `mytheme` create the directory `themes/mytheme`.
|
||||||
|
|
||||||
Inside the theme directory create a directory for each of the types your theme is going to provide. For example to add the login type to the `mytheme`
|
. Inside the theme directory, create a directory for each of the types your theme is going to provide.
|
||||||
theme create the directory `themes/mytheme/login`.
|
+
|
||||||
|
For example, to add the login type to the `mytheme` theme, create the directory `themes/mytheme/login`.
|
||||||
For each type create a file `theme.properties` which allows setting some configuration for the theme. For example to configure the theme `themes/mytheme/login`
|
|
||||||
that we just created to extend the base theme and import some common resources create the file `themes/mytheme/login/theme.properties` with following contents:
|
|
||||||
|
|
||||||
|
. For each type create a file `theme.properties` which allows setting some configuration for the theme.
|
||||||
|
+
|
||||||
|
For example, to configure the theme `themes/mytheme/login` to extend the base theme and import some common resources, create the file `themes/mytheme/login/theme.properties` with following contents:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
parent=base
|
parent=base
|
||||||
import=common/keycloak
|
import=common/keycloak
|
||||||
----
|
----
|
||||||
|
+
|
||||||
|
You have now created a theme with support for the login type.
|
||||||
|
|
||||||
You have now created a theme with support for the login type. To check that it works open the admin console. Select your realm and click on `Themes`.
|
. Log into the Admin Console to checkout your new theme
|
||||||
For `Login Theme` select `mytheme` and click `Save`. Then open the login page for the realm.
|
. Select your realm
|
||||||
|
. Click *Realm Settings* from the menu.
|
||||||
|
. Click on the *Themes* tab.
|
||||||
|
. For *Login Theme* select *mytheme* and click *Save*.
|
||||||
|
. Open the login page for the realm.
|
||||||
|
+
|
||||||
|
You can do this either by logging in through your application or by opening the Account Management console (`/realms/{realm name}/account`).
|
||||||
|
|
||||||
You can do this either by login through your application or by opening the Account Management console (`/realms/{realm name}/account`).
|
. To see the effect of changing the parent theme, set `parent=keycloak` in `theme.properties` and refresh the login page.
|
||||||
|
|
||||||
To see the effect of changing the parent theme, set `parent=keycloak` in `theme.properties` and refresh the login page.
|
[NOTE]
|
||||||
|
====
|
||||||
|
Be sure to re-enable caching in production as it will significantly impact performance.
|
||||||
|
====
|
||||||
|
|
||||||
==== Theme Properties
|
==== Theme properties
|
||||||
|
|
||||||
Theme properties are set in the file `<THEME TYPE>/theme.properties` in the theme directory.
|
Theme properties are set in the file `<THEME TYPE>/theme.properties` in the theme directory.
|
||||||
|
|
||||||
|
@ -133,13 +158,18 @@ unixHome=${env.HOME:Unix home not found}
|
||||||
windowsHome=${env.HOMEPATH:Windows home not found}
|
windowsHome=${env.HOMEPATH:Windows home not found}
|
||||||
----
|
----
|
||||||
|
|
||||||
==== Stylesheets
|
==== Add a stylesheet to a theme
|
||||||
|
|
||||||
A theme can have one or more stylesheets. To add a stylesheet create a file in the `<THEME TYPE>/resources/css` directory of your theme. Then add it to the `styles`
|
You can add one or more stylesheets to a theme.
|
||||||
property in `theme.properties`.
|
|
||||||
|
|
||||||
For example to add `styles.css` to the `mytheme` create `themes/mytheme/login/resources/css/styles.css` with the following content:
|
.Procedure
|
||||||
|
|
||||||
|
. Create a file in the `<THEME TYPE>/resources/css` directory of your theme.
|
||||||
|
|
||||||
|
. Add this file to the `styles` property in `theme.properties`.
|
||||||
|
+
|
||||||
|
For example, to add `styles.css` to the `mytheme`, create `themes/mytheme/login/resources/css/styles.css` with the following content:
|
||||||
|
+
|
||||||
[source,css]
|
[source,css]
|
||||||
----
|
----
|
||||||
.login-pf body {
|
.login-pf body {
|
||||||
|
@ -147,36 +177,43 @@ For example to add `styles.css` to the `mytheme` create `themes/mytheme/login/re
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
Then edit `themes/mytheme/login/theme.properties` and add:
|
. Edit `themes/mytheme/login/theme.properties` and add:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
styles=css/styles.css
|
styles=css/styles.css
|
||||||
----
|
----
|
||||||
|
|
||||||
To see the changes open the login page for your realm. You will notice that the only styles being applied are those from your custom stylesheet. To include the
|
. To see the changes, open the login page for your realm.
|
||||||
styles from the parent theme you need to load the styles from that theme as well. Do this by editing `themes/mytheme/login/theme.properties` and changing `styles`
|
+
|
||||||
to:
|
You will notice that the only styles being applied are those from your custom stylesheet.
|
||||||
|
|
||||||
|
. To include the styles from the parent theme, load the styles from that theme. Edit `themes/mytheme/login/theme.properties` and change `styles` to:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
styles=web_modules/@fontawesome/fontawesome-free/css/icons/all.css web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css css/login.css css/styles.css
|
styles=web_modules/@fontawesome/fontawesome-free/css/icons/all.css web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css css/login.css css/styles.css
|
||||||
----
|
----
|
||||||
|
+
|
||||||
|
NOTE: To override styles from the parent stylesheets, ensure that your stylesheet is listed last.
|
||||||
|
|
||||||
NOTE: To override styles from the parent stylesheets it's important that your stylesheet is listed last.
|
==== Adding a script to a theme
|
||||||
|
|
||||||
==== Scripts
|
You can add one or more scripts to a theme.
|
||||||
|
|
||||||
A theme can have one or more scripts, to add a script create a file in the `<THEME TYPE>/resources/js` directory of your theme. Then add it to the `scripts`
|
.Procedure
|
||||||
property in `theme.properties`.
|
|
||||||
|
|
||||||
For example to add `script.js` to the `mytheme` create `themes/mytheme/login/resources/js/script.js` with the following content:
|
. Create a file in the `<THEME TYPE>/resources/js` directory of your theme.
|
||||||
|
|
||||||
|
. Add the file to the `scripts` property in `theme.properties`.
|
||||||
|
+
|
||||||
|
For example, to add `script.js` to the `mytheme`, create `themes/mytheme/login/resources/js/script.js` with the following content:
|
||||||
|
+
|
||||||
[source,javascript]
|
[source,javascript]
|
||||||
----
|
----
|
||||||
alert('Hello');
|
alert('Hello');
|
||||||
----
|
----
|
||||||
|
+
|
||||||
Then edit `themes/mytheme/login/theme.properties` and add:
|
Then edit `themes/mytheme/login/theme.properties` and add:
|
||||||
|
|
||||||
[source]
|
[source]
|
||||||
|
@ -184,7 +221,7 @@ Then edit `themes/mytheme/login/theme.properties` and add:
|
||||||
scripts=js/script.js
|
scripts=js/script.js
|
||||||
----
|
----
|
||||||
|
|
||||||
==== Images
|
==== Adding an image to a theme
|
||||||
|
|
||||||
To make images available to the theme add them to the `<THEME TYPE>/resources/img` directory of your theme. These can be used from within stylesheets or
|
To make images available to the theme add them to the `<THEME TYPE>/resources/img` directory of your theme. These can be used from within stylesheets or
|
||||||
directly in HTML templates.
|
directly in HTML templates.
|
||||||
|
@ -226,61 +263,73 @@ of the realm.
|
||||||
|
|
||||||
Texts of these message bundles can be overwritten by realm-specific values. The realm-specific values are manageable via UI and API.
|
Texts of these message bundles can be overwritten by realm-specific values. The realm-specific values are manageable via UI and API.
|
||||||
|
|
||||||
==== Internationalization
|
==== Adding a language to a realm
|
||||||
|
|
||||||
{project_name} supports internationalization. To enable internationalization for a realm see {adminguide_link}[{adminguide_name}]. This
|
.Prerequisites
|
||||||
section describes how you can add your own language.
|
|
||||||
|
|
||||||
To add a new language create the file `<THEME TYPE>/messages/messages_<LOCALE>.properties` in the directory of your theme. Then add it to the `locales` property in
|
* To enable internationalization for a realm, see {adminguide_link}[{adminguide_name}].
|
||||||
`<THEME TYPE>/theme.properties`. For a language to be available to users the realms `login`, `account` and `email` theme has to support the language, so you
|
|
||||||
need to add your language for those theme types.
|
|
||||||
|
|
||||||
|
.Procedure
|
||||||
|
|
||||||
|
. Create the file `<THEME TYPE>/messages/messages_<LOCALE>.properties` in the directory of your theme.
|
||||||
|
|
||||||
|
. Add this file to the `locales` property in `<THEME TYPE>/theme.properties`.
|
||||||
|
For a language to be available to users the realms `login`, `account` and `email`, the theme has to support the language, so you need to add your language for those theme types.
|
||||||
|
+
|
||||||
For example, to add Norwegian translations to the `mytheme` theme create the file `themes/mytheme/login/messages/messages_no.properties` with the
|
For example, to add Norwegian translations to the `mytheme` theme create the file `themes/mytheme/login/messages/messages_no.properties` with the
|
||||||
following content:
|
following content:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
usernameOrEmail=Brukernavn
|
usernameOrEmail=Brukernavn
|
||||||
password=Passord
|
password=Passord
|
||||||
----
|
----
|
||||||
|
+
|
||||||
|
If you omit a translation for messages, they will use English.
|
||||||
|
|
||||||
All messages you don't provide a translation for will use the default English translation.
|
. Edit `themes/mytheme/login/theme.properties` and add:
|
||||||
|
+
|
||||||
Then edit `themes/mytheme/login/theme.properties` and add:
|
|
||||||
|
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
locales=en,no
|
locales=en,no
|
||||||
----
|
----
|
||||||
|
|
||||||
You also need to do the same for the `account` and `email` theme types. To do this create `themes/mytheme/account/messages/messages_no.properties` and
|
. Add the same for the `account` and `email` theme types. To do this create `themes/mytheme/account/messages/messages_no.properties` and
|
||||||
`themes/mytheme/email/messages/messages_no.properties`. Leaving these files empty will result in the English messages being used. Then copy
|
`themes/mytheme/email/messages/messages_no.properties`. Leaving these files empty will result in the English messages being used.
|
||||||
`themes/mytheme/login/theme.properties` to `themes/mytheme/account/theme.properties` and `themes/mytheme/email/theme.properties`.
|
|
||||||
|
|
||||||
Finally you need to add a translation for the language selector. This is done by adding a message to the English translation. To do this add the following to
|
. Copy `themes/mytheme/login/theme.properties` to `themes/mytheme/account/theme.properties` and `themes/mytheme/email/theme.properties`.
|
||||||
|
|
||||||
|
. Add a translation for the language selector. This is done by adding a message to the English translation. To do this add the following to
|
||||||
`themes/mytheme/account/messages/messages_en.properties` and `themes/mytheme/login/messages/messages_en.properties`:
|
`themes/mytheme/account/messages/messages_en.properties` and `themes/mytheme/login/messages/messages_en.properties`:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
locale_no=Norsk
|
locale_no=Norsk
|
||||||
----
|
----
|
||||||
|
+
|
||||||
By default message properties files should be encoded using ISO-8859-1. It's also possible to specify the encoding using a special header. For example to use UTF-8 encoding:
|
By default message properties files should be encoded using ISO-8859-1. It's also possible to specify the encoding using a special header. For example to use UTF-8 encoding:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
usernameOrEmail=....
|
usernameOrEmail=....
|
||||||
----
|
----
|
||||||
|
|
||||||
See <<_locale_selector,Locale Selector>> on details on how the current locale is selected.
|
[role="_additional-resources"]
|
||||||
|
.Additional resources
|
||||||
|
* See <<_locale_selector,Locale Selector>> on details on how the current locale is selected.
|
||||||
|
|
||||||
==== Custom Identity providers icons
|
[[custom-identity-providers-icons]]
|
||||||
|
==== Adding custom Identity Providers icons
|
||||||
|
|
||||||
{project_name} supports adding icons for custom Identity providers, which are displayed on the login screen.
|
{project_name} supports adding icons for custom Identity providers, which are displayed on the login screen.
|
||||||
You just have to define icon classes in your login `theme.properties` file (i.e. `themes/mytheme/login/theme.properties`) with key pattern `kcLogoIdP-<alias>`.
|
|
||||||
For Identity provider with an alias `myProvider`, you may add a line, like below, to `theme.properties` file of your custom theme.
|
|
||||||
|
|
||||||
|
.Procedure
|
||||||
|
|
||||||
|
. Define icon classes in your login `theme.properties` file (for example, `themes/mytheme/login/theme.properties`) with key pattern `kcLogoIdP-<alias>`.
|
||||||
|
|
||||||
|
. For an Identity Provider with an alias `myProvider`, you may add a line to `theme.properties` file of your custom theme. For example:
|
||||||
|
+
|
||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
kcLogoIdP-myProvider = fa fa-lock
|
kcLogoIdP-myProvider = fa fa-lock
|
||||||
|
@ -289,18 +338,20 @@ kcLogoIdP-myProvider = fa fa-lock
|
||||||
All icons are available on the official website of PatternFly4.
|
All icons are available on the official website of PatternFly4.
|
||||||
Icons for social providers are already defined in base login theme properties (`themes/keycloak/login/theme.properties`), where you can inspire yourself.
|
Icons for social providers are already defined in base login theme properties (`themes/keycloak/login/theme.properties`), where you can inspire yourself.
|
||||||
|
|
||||||
==== HTML Templates
|
==== Creating a custom HTML template
|
||||||
|
|
||||||
{project_name} uses https://freemarker.apache.org/[Freemarker Templates] in order to generate HTML. You can override individual templates in your own theme by
|
{project_name} uses Freemarker Templates to generate HTML. You can override individual templates in your own theme by
|
||||||
creating `<THEME TYPE>/<TEMPLATE>.ftl`. For a list of templates used see `themes/base/<THEME TYPE>`.
|
creating `<THEME TYPE>/<TEMPLATE>.ftl`. For a list of templates used see `themes/base/<THEME TYPE>`.
|
||||||
|
|
||||||
When creating a custom template it is a good idea to copy the template from the base theme to your own theme, then applying the modifications you need. Bear in
|
.Procedure
|
||||||
mind when upgrading to a new version of {project_name} you may need to update your custom templates to apply changes to the original template if
|
|
||||||
applicable.
|
|
||||||
|
|
||||||
For example to create a custom login form for the `mytheme` theme copy `themes/base/login/login.ftl` to `themes/mytheme/login` and open it in an editor.
|
. Copy the template from the base theme to your own theme.
|
||||||
After the first line (<#import ...>) add `<h1>HELLO WORLD!</h1>` like so:
|
|
||||||
|
|
||||||
|
. Apply the modifications you need.
|
||||||
|
+
|
||||||
|
For example, to create a custom login form for the `mytheme` theme, copy `themes/base/login/login.ftl` to `themes/mytheme/login` and open it in an editor.
|
||||||
|
After the first line (<#import ...>) add `<h1>HELLO WORLD!</h1>`:
|
||||||
|
+
|
||||||
[source,html]
|
[source,html]
|
||||||
----
|
----
|
||||||
<#import "template.ftl" as layout>
|
<#import "template.ftl" as layout>
|
||||||
|
@ -308,7 +359,11 @@ After the first line (<#import ...>) add `<h1>HELLO WORLD!</h1>` like so:
|
||||||
...
|
...
|
||||||
----
|
----
|
||||||
|
|
||||||
Check out the https://freemarker.apache.org/docs/index.html[FreeMarker Manual] for more details on how to edit templates.
|
. Back up the modified template. When upgrading to a new version of {project_name} you may need to update your custom templates to apply changes to the original template if applicable.
|
||||||
|
|
||||||
|
[role="_additional-resources"]
|
||||||
|
.Additional resources
|
||||||
|
* See https://freemarker.apache.org/docs/index.html[FreeMarker Manual] for details on how to edit templates.
|
||||||
|
|
||||||
==== Emails
|
==== Emails
|
||||||
|
|
||||||
|
@ -331,11 +386,14 @@ Themes can be deployed to {project_name} by copying the theme directory to `them
|
||||||
theme to the `themes` directory, but in production you may want to consider using an `archive`. An `archive` makes it simpler to have a versioned copy of
|
theme to the `themes` directory, but in production you may want to consider using an `archive`. An `archive` makes it simpler to have a versioned copy of
|
||||||
the theme, especially when you have multiple instances of {project_name} for example with clustering.
|
the theme, especially when you have multiple instances of {project_name} for example with clustering.
|
||||||
|
|
||||||
To deploy a theme as an archive you need to create a JAR archive with the theme resources. You also need to add a file `META-INF/keycloak-themes.json` to the
|
.Procedure
|
||||||
|
|
||||||
|
. To deploy a theme as an archive, JAR archive with the theme resources.
|
||||||
|
. Add a file `META-INF/keycloak-themes.json` to the
|
||||||
archive that lists the available themes in the archive as well as what types each theme provides.
|
archive that lists the available themes in the archive as well as what types each theme provides.
|
||||||
|
+
|
||||||
For example for the `mytheme` theme create `mytheme.jar` with the contents:
|
For example for the `mytheme` theme create `mytheme.jar` with the contents:
|
||||||
|
+
|
||||||
* META-INF/keycloak-themes.json
|
* META-INF/keycloak-themes.json
|
||||||
* themes/mytheme/login/theme.properties
|
* themes/mytheme/login/theme.properties
|
||||||
* themes/mytheme/login/login.ftl
|
* themes/mytheme/login/login.ftl
|
||||||
|
@ -345,7 +403,7 @@ For example for the `mytheme` theme create `mytheme.jar` with the contents:
|
||||||
* themes/mytheme/email/messages/messages_en.properties
|
* themes/mytheme/email/messages/messages_en.properties
|
||||||
|
|
||||||
The contents of `META-INF/keycloak-themes.json` in this case would be:
|
The contents of `META-INF/keycloak-themes.json` in this case would be:
|
||||||
|
+
|
||||||
[source,json]
|
[source,json]
|
||||||
----
|
----
|
||||||
{
|
{
|
||||||
|
@ -355,8 +413,8 @@ The contents of `META-INF/keycloak-themes.json` in this case would be:
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
+
|
||||||
A single archive can contain multiple themes and each theme can support one or more types.
|
A single archive can contain multiple themes and each theme can support one or more types.
|
||||||
|
|
||||||
To deploy the archive to {project_name} simply drop it into the `standalone/deployments/` directory of
|
. To deploy the archive to {project_name}, add it to the `standalone/deployments/` directory of
|
||||||
{project_name} and it will be automatically loaded.
|
{project_name} and it will be automatically loaded.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[[_user_federation_mapper]]
|
[[_user_federation_mapper]]
|
||||||
== User Federation Mapper SPI
|
== User federation mapper SPI
|
||||||
|
|
||||||
=== LDAP Mapper
|
=== LDAP mapper
|
||||||
|
|
||||||
For the more advanced usecases, you have the possibility to create your own implementation of LDAP mapper or just subclass from some already existing mapper
|
For the more advanced usecases, you have the possibility to create your own implementation of LDAP mapper or just subclass from some already existing mapper
|
||||||
implementation. You will need to implement `UserFederationMapperFactory` interface.
|
implementation. You will need to implement `UserFederationMapperFactory` interface.
|
||||||
|
|
|
@ -97,7 +97,7 @@ public interface CachedUserModel extends UserModel {
|
||||||
This `CachedUserModel` interface allows you to evict the user from the cache and get the provider `UserModel` instance.
|
This `CachedUserModel` interface allows you to evict the user from the cache and get the provider `UserModel` instance.
|
||||||
The `getCachedWith()` method returns a map that allows you to cache additional information pertaining to the user. For example, credentials are not part of the `UserModel` interface. If you wanted to cache credentials in memory, you would implement `OnUserCache` and cache your user's credentials using the `getCachedWith()` method.
|
The `getCachedWith()` method returns a map that allows you to cache additional information pertaining to the user. For example, credentials are not part of the `UserModel` interface. If you wanted to cache credentials in memory, you would implement `OnUserCache` and cache your user's credentials using the `getCachedWith()` method.
|
||||||
|
|
||||||
==== Cache Policies
|
==== Cache policies
|
||||||
|
|
||||||
On the administration console management page for your user storage provider, you can specify a unique cache policy.
|
On the administration console management page for your user storage provider, you can specify a unique cache policy.
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
|
||||||
=== Configuration Techniques
|
=== Configuration Techniques
|
||||||
|
|
||||||
Our `PropertyFileUserStorageProvider` example is bit contrived. It is hardcoded to a property file that is embedded in the jar of the provider, which is not terribly useful. We might want to make the location of this file configurable per instance of the provider. In other words, we might want to reuse this provider multiple times in multiple different realms and point to completely different user property files. We'll also want to perform this configuration within the administration console UI.
|
Our `PropertyFileUserStorageProvider` example is a bit contrived. It is hardcoded to a property file that is embedded in the jar of the provider, which is not terribly useful. We might want to make the location of this file configurable per instance of the provider. In other words, we might want to reuse this provider multiple times in multiple different realms and point to completely different user property files. We'll also want to perform this configuration within the Admin Console UI.
|
||||||
|
|
||||||
The `UserStorageProviderFactory` has additional methods you can implement that handle provider configuration. You describe the variables you want to configure per provider and the administration console automatically renders a generic input page to gather this configuration. When implemented, callback methods also validate the configuration before it is saved, when a provider is created for the first time, and when it is updated. `UserStorageProviderFactory` inherits these methods from the `org.keycloak.component.ComponentFactory` interface.
|
The `UserStorageProviderFactory` has additional methods you can implement that handle provider configuration. You describe the variables you want to configure per provider and the Admin Console automatically renders a generic input page to gather this configuration. When implemented, callback methods also validate the configuration before it is saved, when a provider is created for the first time, and when it is updated. `UserStorageProviderFactory` inherits these methods from the `org.keycloak.component.ComponentFactory` interface.
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
|
@ -29,7 +29,7 @@ The `UserStorageProviderFactory` has additional methods you can implement that h
|
||||||
|
|
||||||
The `ComponentFactory.getConfigProperties()` method returns a list of `org.keycloak.provider.ProviderConfigProperty` instances. These instances declare metadata that is needed to render and store each configuration variable of the provider.
|
The `ComponentFactory.getConfigProperties()` method returns a list of `org.keycloak.provider.ProviderConfigProperty` instances. These instances declare metadata that is needed to render and store each configuration variable of the provider.
|
||||||
|
|
||||||
==== Configuration Example
|
==== Configuration example
|
||||||
|
|
||||||
Let's expand our `PropertyFileUserStorageProviderFactory` example to allow you to point a provider instance to a specific file on disk.
|
Let's expand our `PropertyFileUserStorageProviderFactory` example to allow you to point a provider instance to a specific file on disk.
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ public class PropertyFileUserStorageProviderFactory
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
The `ProviderConfigurationBuilder` class is a great helper class to create a list of configuration properties. Here we specify a variable named `path` that is a String type. On the administration console configuration page for this provider, this configuration variable is labeled as `Path` and has a default value of `${jboss.server.config.dir}/example-users.properties`. When you hover over the tooltip of this configuration option, it displays the help text, `File path to properties file`.
|
The `ProviderConfigurationBuilder` class is a great helper class to create a list of configuration properties. Here we specify a variable named `path` that is a String type. On the Admin Console configuration page for this provider, this configuration variable is labeled as `Path` and has a default value of `${jboss.server.config.dir}/example-users.properties`. When you hover over the tooltip of this configuration option, it displays the help text, `File path to properties file`.
|
||||||
|
|
||||||
The next thing we want to do is to verify that this file exists on disk. We do not want to enable an instance of this provider in the realm unless it points to a valid user property file. To do this, we implement the `validateConfiguration()` method.
|
The next thing we want to do is to verify that this file exists on disk. We do not want to enable an instance of this provider in the realm unless it points to a valid user property file. To do this, we implement the `validateConfiguration()` method.
|
||||||
|
|
||||||
|
@ -101,9 +101,9 @@ Next thing we have to do is remove the old `init()` method. We do this because u
|
||||||
|
|
||||||
This logic is, of course, inefficient as every transaction reads the entire user property file from disk, but hopefully this illustrates, in a simple way, how to hook in configuration variables.
|
This logic is, of course, inefficient as every transaction reads the entire user property file from disk, but hopefully this illustrates, in a simple way, how to hook in configuration variables.
|
||||||
|
|
||||||
==== Configuring the Provider in the Administration Console
|
==== Configuring the provider in the Admin Console
|
||||||
|
|
||||||
Now that the configuration is enabled, you can set the `path` variable when you configure the provider in the administration console.
|
Now that the configuration is enabled, you can set the `path` variable when you configure the provider in the Admin Console.
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
ifeval::[{project_community}==true]
|
||||||
.Configured Provider
|
.Configured Provider
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Import Implementation Strategy
|
=== Import implementation strategy
|
||||||
|
|
||||||
When implementing a user storage provider, there's another strategy you can take. Instead of using user federated storage,
|
When implementing a user storage provider, there's another strategy you can take. Instead of using user federated storage,
|
||||||
you can create a user locally in the {project_name} built-in user database and copy attributes from your external
|
you can create a user locally in the {project_name} built-in user database and copy attributes from your external
|
||||||
|
@ -77,7 +77,7 @@ NOTE: If your provider is implementing the `UserRegistrationProvider` interface,
|
||||||
note that `removeUser()` will be invoked before it is removed from local storage.
|
note that `removeUser()` will be invoked before it is removed from local storage.
|
||||||
|
|
||||||
|
|
||||||
==== ImportedUserValidation Interface
|
==== ImportedUserValidation interface
|
||||||
|
|
||||||
If you remember earlier in this chapter, we discussed how querying for a user worked. Local storage is queried first,
|
If you remember earlier in this chapter, we discussed how querying for a user worked. Local storage is queried first,
|
||||||
if the user is found there, then the query ends. This is a problem for our above implementation as we want
|
if the user is found there, then the query ends. This is a problem for our above implementation as we want
|
||||||
|
@ -104,7 +104,7 @@ Whenever a linked local user is loaded, if the user storage provider class imple
|
||||||
new `UserModel` will be used. You can also optionally do a check to see if the user still exists in the external store.
|
new `UserModel` will be used. You can also optionally do a check to see if the user still exists in the external store.
|
||||||
If `validate()` returns `null`, then the local user will be removed from the database.
|
If `validate()` returns `null`, then the local user will be removed from the database.
|
||||||
|
|
||||||
==== ImportSynchronization Interface
|
==== ImportSynchronization interface
|
||||||
|
|
||||||
With the import strategy you can see that it is possible for the local user copy to get out of sync with
|
With the import strategy you can see that it is possible for the local user copy to get out of sync with
|
||||||
external storage. For example, maybe a user has been removed from the external store. The User Storage SPI has
|
external storage. For example, maybe a user has been removed from the external store. The User Storage SPI has
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Migrating from an Earlier User Federation SPI
|
=== Migrating from an earlier user federation SPI
|
||||||
|
|
||||||
NOTE: This chapter is only applicable if you have implemented a provider using the earlier (and now removed)
|
NOTE: This chapter is only applicable if you have implemented a provider using the earlier (and now removed)
|
||||||
User Federation SPI.
|
User Federation SPI.
|
||||||
|
@ -9,7 +9,7 @@ this earlier SPI available as well. This earlier User Federation SPI has been re
|
||||||
However, if you have written a provider with this earlier SPI, this chapter discusses some strategies you can use to port it.
|
However, if you have written a provider with this earlier SPI, this chapter discusses some strategies you can use to port it.
|
||||||
|
|
||||||
|
|
||||||
==== Import vs. Non-Import
|
==== Import versus non-import
|
||||||
|
|
||||||
The earlier User Federation SPI required you to create a local copy of a user in the {project_name}'s database
|
The earlier User Federation SPI required you to create a local copy of a user in the {project_name}'s database
|
||||||
and import information from your external store to the local copy. However, this is no longer a requirement. You can still
|
and import information from your external store to the local copy. However, this is no longer a requirement. You can still
|
||||||
|
@ -31,7 +31,7 @@ storage approach will only store extra data as needed and might never be used de
|
||||||
* With the import approach, you have to keep local {project_name} storage and external storage in sync. The User Storage SPI
|
* With the import approach, you have to keep local {project_name} storage and external storage in sync. The User Storage SPI
|
||||||
has capability interfaces that you can implement to support synchronization, but this can quickly become painful and messy.
|
has capability interfaces that you can implement to support synchronization, but this can quickly become painful and messy.
|
||||||
|
|
||||||
==== UserFederationProvider vs. UserStorageProvider
|
==== UserFederationProvider versus UserStorageProvider
|
||||||
|
|
||||||
The first thing to notice is that `UserFederationProvider` was a complete interface. You implemented every method in this interface. However, `UserStorageProvider` has instead broken up this interface into multiple capability interfaces that you implement as needed.
|
The first thing to notice is that `UserFederationProvider` was a complete interface. You implemented every method in this interface. However, `UserStorageProvider` has instead broken up this interface into multiple capability interfaces that you implement as needed.
|
||||||
|
|
||||||
|
@ -65,13 +65,13 @@ The `UserFederationProvider` query methods such as `searchByAttributes()` and `g
|
||||||
in an optional interface `UserQueryProvider`. If you do not implement this interface, then users will not be viewable
|
in an optional interface `UserQueryProvider`. If you do not implement this interface, then users will not be viewable
|
||||||
in the admin console. You'll still be able to login though.
|
in the admin console. You'll still be able to login though.
|
||||||
|
|
||||||
==== UserFederationProviderFactory vs. UserStorageProviderFactory
|
==== UserFederationProviderFactory versus UserStorageProviderFactory
|
||||||
|
|
||||||
The synchronization methods in the earlier SPI are now encapsulated within an optional `ImportSynchronization` interface.
|
The synchronization methods in the earlier SPI are now encapsulated within an optional `ImportSynchronization` interface.
|
||||||
If you have implemented synchronization logic, then have your new `UserStorageProviderFactory` implement the
|
If you have implemented synchronization logic, then have your new `UserStorageProviderFactory` implement the
|
||||||
`ImportSynchronization` interface.
|
`ImportSynchronization` interface.
|
||||||
|
|
||||||
==== Upgrading to a New Model
|
==== Upgrading to a new model
|
||||||
|
|
||||||
The User Storage SPI instances are stored in a different set of relational tables. {project_name}
|
The User Storage SPI instances are stored in a different set of relational tables. {project_name}
|
||||||
automatically runs a migration script. If any earlier User Federation providers are deployed for a realm, they are converted
|
automatically runs a migration script. If any earlier User Federation providers are deployed for a realm, they are converted
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Model Interfaces
|
=== Model interfaces
|
||||||
|
|
||||||
Most of the methods defined in the _capability_ _interfaces_ either return or are passed in representations of a user. These representations are defined by the `org.keycloak.models.UserModel` interface. App developers are required to implement this interface. It provides a mapping between the external user store and the user metamodel that {project_name} uses.
|
Most of the methods defined in the _capability_ _interfaces_ either return or are passed in representations of a user. These representations are defined by the `org.keycloak.models.UserModel` interface. App developers are required to implement this interface. It provides a mapping between the external user store and the user metamodel that {project_name} uses.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Packaging and Deployment
|
=== Packaging and deployment
|
||||||
|
|
||||||
User Storage providers are packaged in a JAR and deployed or undeployed to the {project_name} runtime in the same way you would deploy something in the {appserver_name} application server. You can either copy the JAR directly to the `standalone/deployments/` directory of the server, or use the JBoss CLI to execute the deployment.
|
User Storage providers are packaged in a JAR and deployed or undeployed to the {project_name} runtime in the same way you would deploy something in the {appserver_name} application server. You can either copy the JAR directly to the `standalone/deployments/` directory of the server, or use the JBoss CLI to execute the deployment.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[[_provider_capability_interfaces]]
|
[[_provider_capability_interfaces]]
|
||||||
|
|
||||||
=== Provider Capability Interfaces
|
=== Provider capability interfaces
|
||||||
|
|
||||||
If you have examined the `UserStorageProvider` interface closely you might notice that it does not define any methods for locating or managing users. These methods are actually defined in other _capability interfaces_ depending on what scope of capabilities your external user store can provide and execute on. For example, some external stores are read-only and can only do simple queries and credential validation. You will only be required to implement the _capability interfaces_ for the features you are able to. You can implement these interfaces:
|
If you have examined the `UserStorageProvider` interface closely you might notice that it does not define any methods for locating or managing users. These methods are actually defined in other _capability interfaces_ depending on what scope of capabilities your external user store can provide and execute on. For example, some external stores are read-only and can only do simple queries and credential validation. You will only be required to implement the _capability interfaces_ for the features you are able to. You can implement these interfaces:
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Provider Interfaces
|
=== Provider interfaces
|
||||||
|
|
||||||
When building an implementation of the User Storage SPI you have to define a provider class and a provider factory.
|
When building an implementation of the User Storage SPI you have to define a provider class and a provider factory.
|
||||||
Provider class instances are created per transaction by provider factories.
|
Provider class instances are created per transaction by provider factories.
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
|
||||||
=== Add/Remove User and Query Capability interfaces
|
=== Add/Remove user and query capability interfaces
|
||||||
|
|
||||||
One thing we have not done with our example is allow it to add and remove users or change passwords. Users defined in our example are
|
One thing we have not done with our example is allow it to add and remove users or change passwords. Users defined in our example are
|
||||||
also not queryable or viewable in the administration console. To add these enhancements, our example provider must implement
|
also not queryable or viewable in the Admin Console. To add these enhancements, our example provider must implement
|
||||||
the `UserQueryProvider` and `UserRegistrationProvider` interfaces.
|
the `UserQueryProvider` and `UserRegistrationProvider` interfaces.
|
||||||
|
|
||||||
==== Implementing UserRegistrationProvider
|
==== Implementing UserRegistrationProvider
|
||||||
|
|
||||||
To implement adding and removing users from this particular store, we first have to be able to save our properties
|
Use this procedure to implement adding and removing users from the particular store, we first have to be able to save our properties
|
||||||
file to disk.
|
file to disk.
|
||||||
|
|
||||||
.PropertyFileUserStorageProvider
|
.PropertyFileUserStorageProvider
|
||||||
|
@ -120,11 +120,11 @@ We can now also implement disabling a password.
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
With these methods implemented, you'll now be able to change and disable the password for the user in the administration console.
|
With these methods implemented, you'll now be able to change and disable the password for the user in the Admin Console.
|
||||||
|
|
||||||
==== Implementing UserQueryProvider
|
==== Implementing UserQueryProvider
|
||||||
|
|
||||||
Without implementing `UserQueryProvider` the administration console would not be able to view and manage users that were loaded
|
Without implementing `UserQueryProvider` the Admin Console would not be able to view and manage users that were loaded
|
||||||
by our example provider. Let's look at implementing this interface.
|
by our example provider. Let's look at implementing this interface.
|
||||||
|
|
||||||
.PropertyFileUserStorageProvider
|
.PropertyFileUserStorageProvider
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== REST Management API
|
=== REST management API
|
||||||
|
|
||||||
You can create, remove, and update your user storage provider deployments through the administrator REST API. The User Storage SPI
|
You can create, remove, and update your user storage provider deployments through the administrator REST API. The User Storage SPI
|
||||||
is built on top of a generic component interface so you will be using that generic API to manage your providers.
|
is built on top of a generic component interface so you will be using that generic API to manage your providers.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
=== Simple Read-Only, Lookup Example
|
=== Simple read-only, lookup example
|
||||||
|
|
||||||
To illustrate the basics of implementing the User Storage SPI let's walk through a simple example. In this chapter you'll see the implementation of a simple `UserStorageProvider` that looks up users in a simple property file. The property file contains username and password definitions and is hardcoded to a specific location on the classpath. The provider will be able to look up the user by ID and username and also be able to validate passwords. Users that originate from this provider will be read-only.
|
To illustrate the basics of implementing the User Storage SPI let's walk through a simple example. In this chapter you'll see the implementation of a simple `UserStorageProvider` that looks up users in a simple property file. The property file contains username and password definitions and is hardcoded to a specific location on the classpath. The provider will be able to look up the user by ID and username and also be able to validate passwords. Users that originate from this provider will be read-only.
|
||||||
|
|
||||||
==== Provider Class
|
==== Provider class
|
||||||
|
|
||||||
The first thing we will walk through is the `UserStorageProvider` class.
|
The first thing we will walk through is the `UserStorageProvider` class.
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ Our provider class, `PropertyFileUserStorageProvider`, implements many interface
|
||||||
|
|
||||||
The constructor for this provider class is going to store the reference to the `KeycloakSession`, `ComponentModel`, and property file. We'll use all of these later. Also notice that there is a map of loaded users. Whenever we find a user we will store it in this map so that we avoid re-creating it again within the same transaction. This is a good practice to follow as many providers will need to do this (that is, any provider that integrates with JPA). Remember also that provider class instances are created once per transaction and are closed after the transaction completes.
|
The constructor for this provider class is going to store the reference to the `KeycloakSession`, `ComponentModel`, and property file. We'll use all of these later. Also notice that there is a map of loaded users. Whenever we find a user we will store it in this map so that we avoid re-creating it again within the same transaction. This is a good practice to follow as many providers will need to do this (that is, any provider that integrates with JPA). Remember also that provider class instances are created once per transaction and are closed after the transaction completes.
|
||||||
|
|
||||||
===== UserLookupProvider Implementation
|
===== UserLookupProvider implementation
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
|
@ -92,7 +92,7 @@ The `getUserById()` method parses the `id` parameter using the `org.keycloak.sto
|
||||||
|
|
||||||
Emails are not stored, so the `getUserByEmail()` method returns null.
|
Emails are not stored, so the `getUserByEmail()` method returns null.
|
||||||
|
|
||||||
===== CredentialInputValidator Implementation
|
===== CredentialInputValidator implementation
|
||||||
|
|
||||||
Next let's look at the method implementations for `CredentialInputValidator`.
|
Next let's look at the method implementations for `CredentialInputValidator`.
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ The `supportsCredentialType()` method returns whether validation is supported fo
|
||||||
|
|
||||||
The `isValid()` method is responsible for validating passwords. The `CredentialInput` parameter is really just an abstract interface for all credential types. We make sure that we support the credential type and also that it is an instance of `UserCredentialModel`. When a user logs in through the login page, the plain text of the password input is put into an instance of `UserCredentialModel`. The `isValid()` method checks this value against the plain text password stored in the properties file. A return value of `true` means the password is valid.
|
The `isValid()` method is responsible for validating passwords. The `CredentialInput` parameter is really just an abstract interface for all credential types. We make sure that we support the credential type and also that it is an instance of `UserCredentialModel`. When a user logs in through the login page, the plain text of the password input is put into an instance of `UserCredentialModel`. The `isValid()` method checks this value against the plain text password stored in the properties file. A return value of `true` means the password is valid.
|
||||||
|
|
||||||
===== CredentialInputUpdater Implementation
|
===== CredentialInputUpdater implementation
|
||||||
|
|
||||||
As noted before, the only reason we implement the `CredentialInputUpdater` interface in this example is to forbid modifications of user passwords. The reason we have to do this is because otherwise the runtime would allow the password to be overridden in {project_name} local storage. We'll talk more about this later in this chapter.
|
As noted before, the only reason we implement the `CredentialInputUpdater` interface in this example is to forbid modifications of user passwords. The reason we have to do this is because otherwise the runtime would allow the password to be overridden in {project_name} local storage. We'll talk more about this later in this chapter.
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ As noted before, the only reason we implement the `CredentialInputUpdater` inter
|
||||||
|
|
||||||
The `updateCredential()` method just checks to see if the credential type is password. If it is, a `ReadOnlyException` is thrown.
|
The `updateCredential()` method just checks to see if the credential type is password. If it is, a `ReadOnlyException` is thrown.
|
||||||
|
|
||||||
==== Provider Factory Implementation
|
==== Provider factory implementation
|
||||||
|
|
||||||
Now that the provider class is complete, we now turn our attention to the provider factory class.
|
Now that the provider class is complete, we now turn our attention to the provider factory class.
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ public void init(Config.Scope config) {
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
===== Create Method
|
===== Create method
|
||||||
|
|
||||||
Our last step in creating the provider factory is the `create()` method.
|
Our last step in creating the provider factory is the `create()` method.
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ Our last step in creating the provider factory is the `create()` method.
|
||||||
|
|
||||||
We simply allocate the `PropertyFileUserStorageProvider` class. This create method will be called once per transaction.
|
We simply allocate the `PropertyFileUserStorageProvider` class. This create method will be called once per transaction.
|
||||||
|
|
||||||
==== Packaging and Deployment
|
==== Packaging and deployment
|
||||||
|
|
||||||
The class files for our provider implementation should be placed in a jar. You also have to declare the provider factory class within the `META-INF/services/org.keycloak.storage.UserStorageProviderFactory` file.
|
The class files for our provider implementation should be placed in a jar. You also have to declare the provider factory class within the `META-INF/services/org.keycloak.storage.UserStorageProviderFactory` file.
|
||||||
|
|
||||||
|
@ -258,25 +258,34 @@ org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
|
||||||
|
|
||||||
Once you create the jar you can deploy it using regular {appserver_name} means: copy the jar into the `standalone/deployments/` directory or using the JBoss CLI.
|
Once you create the jar you can deploy it using regular {appserver_name} means: copy the jar into the `standalone/deployments/` directory or using the JBoss CLI.
|
||||||
|
|
||||||
==== Enabling the Provider in the Administration Console
|
==== Enabling the provider in the Admin Console
|
||||||
|
|
||||||
You enable user storage providers per realm within the `User Federation` page in the administration console.
|
You enable user storage providers per realm within the *User Federation* page in the Admin Console.
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
ifeval::[{project_community}==true]
|
||||||
.User Federation
|
.User Federation
|
||||||
image:{project_images}/empty-user-federation-page.png[]
|
image:{project_images}/empty-user-federation-page.png[]
|
||||||
endif::[]
|
endif::[]
|
||||||
|
|
||||||
Select the provider we just created from the list: `readonly-property-file`. It brings you to the configuration page for our provider. We do not have anything to configure, so click *Save*.
|
.Procedure
|
||||||
|
|
||||||
|
. Select the provider we just created from the list: `readonly-property-file`.
|
||||||
|
+
|
||||||
|
The configuration page for our provider displays.
|
||||||
|
|
||||||
|
. Click *Save* because we have nothing to configure.
|
||||||
ifeval::[{project_community}==true]
|
ifeval::[{project_community}==true]
|
||||||
|
+
|
||||||
.Configured Provider
|
.Configured Provider
|
||||||
image:{project_images}/storage-provider-created.png[]
|
image:{project_images}/storage-provider-created.png[]
|
||||||
endif::[]
|
endif::[]
|
||||||
|
|
||||||
When you go back to the main `User Federation` page, you now see your provider listed.
|
. Return to the main *User Federation* page
|
||||||
|
+
|
||||||
|
You now see your provider listed.
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
ifeval::[{project_community}==true]
|
||||||
|
+
|
||||||
.User Federation
|
.User Federation
|
||||||
image:{project_images}/user-federation-page.png[]
|
image:{project_images}/user-federation-page.png[]
|
||||||
endif::[]
|
endif::[]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
=== Stream-based Interfaces
|
=== Stream-based interfaces
|
||||||
|
|
||||||
Many of the user storage interfaces in {project_name} contain query methods that can return potentially large sets of objects,
|
Many of the user storage interfaces in {project_name} contain query methods that can return potentially large sets of objects,
|
||||||
which might lead to significant impacts in terms of memory consumption and processing time. This is especially true when only
|
which might lead to significant impacts in terms of memory consumption and processing time. This is especially true when only
|
||||||
|
|
Loading…
Reference in a new issue