Merge pull request #77 from jenmalloy/RHSSO-940

fixed RHSSO-940
This commit is contained in:
Jen Malloy 2017-03-28 12:02:40 -04:00 committed by GitHub
commit 4d7bc951d6
5 changed files with 52 additions and 129 deletions

View file

@ -1,27 +1,13 @@
[[_user-storage-spi]] [[_user-storage-spi]]
== User Storage SPI == User Storage SPI
The User Storage SPI allows you to write extensions to {{book.project.name}} to connect to external user databases and credential You can use the User Storage SPI to write extensions to {{book.project.name}} to connect to external user databases and credential stores. The built-in LDAP and ActiveDirectory support is an implementation of this SPI in action. Out of the box, {{book.project.name}} uses its local database to create, update, and look up users and validation credentials. Often though, organizations have existing external proprietary user databases that they cannot migrate to {{book.project.name}}'s data model. For those situations, application developers can write implementations of the User Storage SPI to bridge the external user store and the internal user object model that {{book.project.name}} uses to log in users and manage them.
stores. The built-in LDAP and ActiveDirectory support is an implementation of this SPI in action. Out of the box,
{{book.project.name}} uses its local database to create, update, and lookup users and validation credentials from. Often though,
organizations have existing external proprietary user databases that they cannot migrate to {{book.project.name}}'s data model.
For those situations, Application developers can write implementations of the User Storage SPI to bridge between the external user store and the internal
user object model that {{book.project.name}} uses to login users and manage them.
When the {{book.project.name}} runtime needs to look up a user, like when a user is logging in, it performs a number of When the {{book.project.name}} runtime needs to look up a user, such as when a user is logging in, it performs a number of steps to locate the user. It first looks to see if the user is in the user cache; if the user is found it uses that in-memory representation. Then it looks for the user within the {{book.project.name}} local database. If the user is not found, it then loops through User Storage SPI provider implementations to perform the user query until one of them returns the user the runtime is looking for. The provider queries the external user store for the user and maps the external data representation of the user to {{book.project.name}}'s user metamodel.
steps to locate the user. It first looks to see if the user is in the user cache, if its there it uses that in-memory
representation. Then it looks for the user within {{book.project.name}} local database. If its not there, it then
loops through User Storage SPI provider implementations to perform the user query until one of them returns
the user the runtime is looking for. The provider queries the external user store for the user and maps the external data representation
of the user to {{book.project.name}}'s user metamodel.
User Storage SPI provider implementations can also perform complex criteria queries, perform CRUD operations on users, User Storage SPI provider implementations can also perform complex criteria queries, perform CRUD operations on users, validate and manage credentials, or perform bulk updates of many users at once. It depends on the capabilities of the external store.
validate and manage credentials, or perform bulk updates of many users at once. It all depends on the capabilities of
the external store.
User Storage SPI provider implementations are packaged and deployed similarly (and often are) to Java EE components. User Storage SPI provider implementations are packaged and deployed similarly to (and often are) Java EE components. They are not enabled by default, but instead must be enabled and configured per realm under the `User Federation` tab in the administration console.
The are not enabled by default, but instead must be enabled and configured per realm under the `User Federation` tab
in the administration console.

View file

@ -1,9 +1,7 @@
=== 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 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 {{book.project.name}} uses.
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 {{book.project.name}} uses.
[source,java] [source,java]
---- ----
@ -27,33 +25,25 @@ public interface UserModel extends RoleMapperModel {
} }
---- ----
`UserModel` implementations provide access to read and update metadata about the user including things like username, name, email, `UserModel` implementations provide access to read and update metadata about the user including things like username, name, email, role and group mappings, as well as other arbitrary attributes.
role and group mappings, as well as other arbitrary attributes.
There are other model classes within the `org.keycloak.models` package the represent other parts of the {{book.project.name}} There are other model classes within the `org.keycloak.models` package that represent other parts of the {{book.project.name}} metamodel: `RealmModel`, `RoleModel`, `GroupModel`, and `ClientModel`.
metamodel: `RealmModel`, `RoleModel`, `GroupModel`, and `ClientModel`.
==== Storage Ids ==== Storage Ids
One really import method of `UserModel` is the `getId()` method. When implementing `UserModel` developers must be aware One important method of `UserModel` is the `getId()` method. When implementing `UserModel` developers must be aware of the user id format. The format must be:
of the user id format. The format must be
---- ----
"f:" + component id + ":" + external id "f:" + component id + ":" + external id
---- ----
The {{book.project.name}} runtime often has to lookup users by their user id. The user id contains enough information The {{book.project.name}} runtime often has to look up users by their user id. The user id contains enough information so that the runtime does not have to query every single `UserStorageProvider` in the system to find the user.
so that the runtime does not have to query every single `UserStorageProvider` in the system to find the user.
The component id is the id returned from `ComponentModel.getId()`. The `ComponentModel` is passed in as a parameter The component id is the id returned from `ComponentModel.getId()`. The `ComponentModel` is passed in as a parameter when creating the provider class so you can get it from there. The external id is information your provider class needs to find the user in the external store. This is often a username or a uid. For example, it might look something like this:
when creating the provider class so you can get it from there. The external id is information your provider class
needs to find the user in the external store. This is often a username or a uid. For example, it might look something
like this:
---- ----
f:332a234e31234:wburke f:332a234e31234:wburke
---- ----
When the runtime does a lookup by id, the id is parsed to obtain the component id. The component id is used to When the runtime does a lookup by id, the id is parsed to obtain the component id. The component id is used to locate the `UserStorageProvider` that was originally used to load the user. That provider is then passed the id. The provider again parses the id to obtain the external id it will use to locate the user in external user storage.
locate the `UserStorageProvider` that was originally used to load the user. That provider is then passed the id.
The provider again parses the id to obtain the external id it will use to locate the user in external user storage.

View file

@ -1,17 +1,14 @@
=== Packaging and Deployment === Packaging and Deployment
User Storage providers are packaged in a jar and deployed or undeployed to the {{book.project.name}} runtime in the same exact User Storage providers are packaged in a jar and deployed or undeployed to the {{book.project.name}} runtime in the same way you would deploy something in the JBoss/Wildfly application server. You can either copy the jar directly to the `deploy/` directory of the server, or use the JBoss CLI to execute the deployment.
way as you would deploy something in the JBoss/Wildfly application server. You can either copy the jar directly to
the `deploy/` directory if the server, or use the JBoss CLI to execute the deployment. In order for {{book.project.name}} In order for {{book.project.name}} to recognize the provider, you need to add a file to the jar: `META-INF/services/org.keycloak.storage.UserStorageProviderFactory`. This file must contain a line-separated list of fully qualified classnames of the `UserStorageProviderFactory` implementation:
to recognize the provider, there's one special file you need to add to the jar: `META-INF/services/org.keycloak.storage.UserStorageProviderFactory`.
This file must contain a line separated list of fully qualified classnames of use `UserStorageProviderFactory` implementation.
---- ----
org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
---- ----
{{book.project.name}} supports hot deployment of these provider jars. You'll also see later in this chapter that you can {{book.project.name}} supports hot deployment of these provider jars. You'll also see later in this chapter that you can package it within and as Java EE components.
package within and as Java EE components.

View file

@ -1,21 +1,17 @@
=== Provider Capability Interfaces === Provider Capability Interfaces
If you've examined the `UserStorageProvider` interface closely you may be scratching your head a bit because it does If you've 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. Here's a list of interfaces that you can implement:
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. Here's a list of interfaces that you can implement:
|=== |===
|SPI|Description |SPI|Description
|`org.keycloak.storage.user.UserLookupProvider`|This interface is required if you want to be able to login with users from this external store. Most (all?) providers implement this interface. |`org.keycloak.storage.user.UserLookupProvider`|This interface is required if you want to be able to log in with users from this external store. Most (all?) providers implement this interface.
|`org.keycloak.storage.user.UserQueryProvider`|Defines complex queries that are used to locate one or more users. You must implement this interface if you want to view and manager users from the administration console. |`org.keycloak.storage.user.UserQueryProvider`|Defines complex queries that are used to locate one or more users. You must implement this interface if you want to view and manage users from the administration console.
|`org.keycloak.storage.user.UserRegistrationProvider`|Implement this interface if your provider supports adding and removing users. |`org.keycloak.storage.user.UserRegistrationProvider`|Implement this interface if your provider supports adding and removing users.
|`org.keycloak.storage.user.UserBulkUpdateProvider`|Implement this interface if your provider supports bulk update of a set of users. |`org.keycloak.storage.user.UserBulkUpdateProvider`|Implement this interface if your provider supports bulk update of a set of users.
|`org.keycloak.credential.CredentialInputValidator`|Implement this interface if your provider can validate one or more different credential types. (i.e. can validate a password) |`org.keycloak.credential.CredentialInputValidator`|Implement this interface if your provider can validate one or more different credential types (for example, if your provider can validate a password).
|`org.keycloak.credential.CredentialInputUpdater`|Implement this interface if your provider supports updating one more different credential types. |`org.keycloak.credential.CredentialInputUpdater`|Implement this interface if your provider supports updating one or more different credential types.
|=== |===

View file

@ -1,10 +1,6 @@
=== 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 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.
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 lookup 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
@ -22,12 +18,7 @@ public class PropertyFileUserStorageProvider implements
} }
---- ----
Our provider class, `PropertyFileUserStorageProvider`, implements a bunch of interfaces. It implements the Our provider class, `PropertyFileUserStorageProvider`, implements many interfaces. It implements the `UserStorageProvider` as that is a base requirement of the SPI. It implements the `UserLookupProvider` interface because we want to be able to log in with users stored by this provider. It implements the `CredentialInputValidator` interface because we want to be able to validate passwords entered in using the login screen. Our property file is read-only. We implement the `CredentialInputUpdater` because we want to post an error condition when the user attempts to update his password.
`UserStorageProvider` as that is a base requirement of the SPI. It implements the `UserLookupProvider` interface
because we want to be able to login with users stored by this provider. It implements the `CredentialInputValidator`
interface because we want to be able to validate passwords entered in via the login screen. Our property file
is going to be read only. We implement the `CredentialInputUpdater` because was want to post an error condition
when the user's password is attempted to be updated.
[source,java] [source,java]
---- ----
@ -44,13 +35,9 @@ when the user's password is attempted to be updated.
} }
---- ----
The constructor for this provider class is going to store the reference to the `KeycloakSession`, `ComponentModel`, and 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.
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 recreating it again within the same transaction. This is a good practice
to do as many providers will need to do this (i.e., one 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]
---- ----
@ -91,31 +78,21 @@ instances are created once per transaction and are closed after the transaction
---- ----
The `getUserByUsername()` method is invoked by the {{book.project.name}} login page when a user logs in. In our The `getUserByUsername()` method is invoked by the {{book.project.name}} login page when a user logs in. In our implementation we first check the `loadedUsers` map to see if the user has already been loaded within this transaction. If it hasn't been loaded we look in the property file for the username. If it exists we create an implementation of `UserModel`, store it in `loadedUsers` for future reference, and return this instance.
implementation we first check the `loadedUsers` map to see if the user has already been loaded within this transaction.
If it hasn't been loaded we look in the property file for the username. If it exists we create an implementation
of `UserModel`, store it in `loadedUsers` for future reference and return this instance.
The `createAdapter()` method uses the helper class `org.keycloak.storage.adapter.AbstractUserAdapter`. This provides The `createAdapter()` method uses the helper class `org.keycloak.storage.adapter.AbstractUserAdapter`. This provides a base implementation for `UserModel`. It automatically generates a user id based on the required storage id format using the username of the user as the external id.
a base implementation for `UserModel`. It automatically generates a user id based on the required storage id format
using the username of the user as the external id.
---- ----
"f:" + component id + ":" + username "f:" + component id + ":" + username
---- ----
Every get method of `AbstractUserAdapter` either returns null or empty collections. However, methods that return Every get method of `AbstractUserAdapter` either returns null or empty collections. However, methods that return role and group mappings will return the default roles and groups configured for the realm for every user. Every set method of `AbstractUserAdapter` will throw a `org.keycloak.storage.ReadOnlyException`. So if you attempt to modify the user in the admininstration console, you will get an error.
role and group mappings will return the default roles and groups configured for the realm for every user. Every set
method of `AbstractUserAdapter` will throw a `org.keycloak.storage.ReadOnlyException`. So if you attempt
to modify the user in the admin console you will get an error.
The `getUserById()` method parses the `id` parameter using the `org.keycloak.storage.StorageId' helper class. The The `getUserById()` method parses the `id` parameter using the `org.keycloak.storage.StorageId' helper class. The `StorageId.getExternalId()` method is invoked to obtain the username embeded in the `id` parameter. The method then delegates to `getUserByUsername()`.
`StorageId.getExternalId()` method is invoked to obtain the username embeded in the `id` parameter. The method
then delegates to `getUserByUsername()`.
Emails are not stored at all, so the `getUserByEmail() method 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`.
@ -143,23 +120,15 @@ Next let's look at the method implementations for `CredentialInputValidator`.
} }
---- ----
The `isConfiguredFor()` method is called by the runtime to determine if a specific credential type is configured for The `isConfiguredFor()` method is called by the runtime to determine if a specific credential type is configured for the user. This method checks to see that the password is set for the user.
the user. This method checks to see that the password is set for the user.
The `suportsCredentialType()` method returns whether validation is supported for a specific credential type. We check The `suportsCredentialType()` method returns whether validation is supported for a specific credential type. We check to see if the credential type is `password`.
to see if the credential type is `password`.
The `isValid()` method is responsible for validating passwords. The `CredentialInput` parameter is really just an abstract 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.
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 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 overriden in {{book.project.name}} local storage. We'll talk more about this later in this chapter.
user passwords. The reason we have to do this is because otherwise the runtime would allow the password to be overriden
in {{book.project.name}} local storage. We'll talk more about this later in this chapter
[source,java] [source,java]
---- ----
@ -181,10 +150,9 @@ in {{book.project.name}} local storage. We'll talk more about this later in this
} }
---- ----
The `updateCredential()` method just checks to see if the credential type is password. If it is, a `ReadOnlyException` The `updateCredential()` method just checks to see if the credential type is password. If it is, a `ReadOnlyException` is thrown.
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.
@ -201,14 +169,12 @@ public class PropertyFileUserStorageProviderFactory
} }
---- ----
First thing to notice is that when implementing the `UserStorageProviderFactory` class, you must pass in the concrete First thing to notice is that when implementing the `UserStorageProviderFactory` class, you must pass in the concrete provider class implementation as a template parameter. Here we specify the provider class we defined before: `PropertyFileUserStorageProvider`.
provider class implementation as a template parameter. Here we specify the provider class we defined before: `PropertyFileUserStorageProvider`.
WARNING: If you do not specify the template parameter, your provider will not function. The runtime does class introspection WARNING: If you do not specify the template parameter, your provider will not function. The runtime does class introspection
to determine the _capability interfaces_ that the provider implements. to determine the _capability interfaces_ that the provider implements.
The `getId()` method identifies the factory in the runtime and will also be the string shown in the admin console when you want The `getId()` method identifies the factory in the runtime and will also be the string shown in the admin console when you want to enable a user storage provider for the realm.
to enable a user storage provider for the realm.
===== Initialization ===== Initialization
@ -238,17 +204,11 @@ to enable a user storage provider for the realm.
} }
---- ----
The `UserStorageProviderFactory` interface has an optional `init()` method you can implement. When {{book.project.name}} The `UserStorageProviderFactory` interface has an optional `init()` method you can implement. When {{book.project.name}} boots up, one and only one instance of each different provider factory. Also at boot time, the `init()` method will be called on each one of these factory instances. There's also a `postInit()` method you can implement as well. After each factory's `init()` method is invoked, their `postInit()` methods will be called.
boots up, one and only one instance of each different provider factory. Also at boot time, the `init()` method will
be called on each one of these factory instances. There's also a `postInit()` method you can implement as well. After
each factory's `init()` method is invoked, their `postInit()` methods will be called.
In our `init()` method implementation, we find the property file containing our user declarations from the classpath. In our `init()` method implementation, we find the property file containing our user declarations from the classpath. We then load the `properties` field with the username and password combinations stored there.
We then load the `properties` field with the username and password combinations stored there.
The `Config.Scope` parameter is factory configuration that can be set up The `Config.Scope` parameter is factory configuration that can be set up within `standalone.xml`, `standalone-ha.xml`, or `domain.xml`. For more information on where the `standalone.xml`, `standalone-ha.xml`, or `domain.xml` file resides see the link:{{book.installguide.link}}[{{book.installguide.name}}].
within `standalone.xml`, `standalone-ha.xml`, or `domain.xml`.
For more information on where the `standalone.xml`, `standalone-ha.xml`, or `domain.xml` file resides see the link:{{book.installguide.link}}[{{book.installguide.name}}].
For example, by adding the following to `standalone.xml`: For example, by adding the following to `standalone.xml`:
@ -263,8 +223,7 @@ For example, by adding the following to `standalone.xml`:
</spi> </spi>
---- ----
We can specify the classpath of the user property file instead of hard coded it. We can specify the classpath of the user property file instead of hard coded it. Then you can retrieve the config in the `PropertyFileUserStorageProviderFactory.init()`:
Then you can retrieve the config in the `PropertyFileUserStorageProviderFactory.init()` :
[source,java] [source,java]
---- ----
@ -276,7 +235,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.
@ -292,27 +251,24 @@ We simply allocate the `PropertyFileUserStorageProvider` class. This create met
==== 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 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.
factory class within the `META-INF/services/org.keycloak.storage.UserStorageProviderFactory` file.
---- ----
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
---- ----
Once you create the jar you can deploy it using regular JBoss/Wildfly means: copy the jar into the `deploy/` directory Once you create the jar you can deploy it using regular JBoss/Wildfly means: copy the jar into the `deploy/` directory or using the JBoss CLI.
or using the JBoss CLI.
==== Enabling the Provider in Admin Console ==== Enabling the Provider in the Administration Console
You enable user storage providers per realm within the `User Federation` page in the admin console. You enable user storage providers per realm within the `User Federation` page in the administration console.
{% if book.community %} {% if book.community %}
.User Federation .User Federation
image:../../{{book.images}}/empty-user-federation-page.png[] image:../../{{book.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 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*.
page for our provider. We do not have anything to configure, so click *Save*.
{% if book.community %} {% if book.community %}
.Configured Provider .Configured Provider
@ -326,6 +282,4 @@ When you go back to the main `User Federation` page, you now see your provider l
image:../../{{book.images}}/user-federation-page.png[] image:../../{{book.images}}/user-federation-page.png[]
{% endif %} {% endif %}
You will now be able to log in with a user declared in the `users.properties` file. Of course, this user will have You will now be able to log in with a user declared in the `users.properties` file. Of course, this user will have zero permissions to do anything and will be read only. You can though view the user on its account page after you log in.
zero permissions to do anything and will be read only. You can though view the user on its account page after you
login.