7.5. 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.
7.5.1. Provider Class リンクのコピーリンクがクリップボードにコピーされました!
The first thing we will walk through is the UserStorageProvider
class.
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.
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.
7.5.1.1. UserLookupProvider Implementation リンクのコピーリンクがクリップボードにコピーされました!
The getUserByUsername()
method is invoked by the Red Hat Single Sign-On 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.
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.
"f:" + component id + ":" + username
"f:" + component id + ":" + username
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.
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()
.
Emails are not stored, so the getUserByEmail()
method returns null.
7.5.1.2. CredentialInputValidator Implementation リンクのコピーリンクがクリップボードにコピーされました!
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 user. This method checks to see that the password is set for the user.
The supportsCredentialType()
method returns whether validation is supported for a specific credential type. We check to see if the credential type is password
.
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.
7.5.1.3. 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 Red Hat Single Sign-On local storage. We’ll talk more about this later in this chapter.
The updateCredential()
method just checks to see if the credential type is password. If it is, a ReadOnlyException
is thrown.