more user storage

This commit is contained in:
Bill Burke 2016-12-01 21:57:17 -05:00
parent 70e2f0b307
commit 8588ea9cd0
7 changed files with 166 additions and 0 deletions

View file

@ -16,6 +16,7 @@
},
"community": false,
"product": true,
"images": "rhsso-images",
"adminguide": {
"name": "Server Administration Guide",
"link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.0/server-administration-guide/"

View file

@ -16,6 +16,7 @@
},
"community": true,
"product": false,
"images": "keycloak-images",
"adminguide": {
"name": "Server Administration Guide",
"link": "https://keycloak.gitbooks.io/server-adminstration-guide/content/"

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View file

@ -528,6 +528,170 @@ We simply allocate the `PropertyFileUserStorageProvider` class. This create met
==== 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.
----
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
or using the JBoss CLI.
==== Enabling the Provider in Admin Console
You enable user storage providers per realm within the `User Federation` page in the admin console.
.User Federation
image:../{{book.images}}/empty-user-federation-page.png[]
Select the provider we just created from the list: `readonly-property-file`. It brings you to the configuration
page for our provider. We don't have anything to configure, so just click the `Save` button.
.Configured Provider
image:../{{book.images}}/storage-provider-created.png[]
When you go back to the main `User Federation` page, you'll now see your provider listed.
.User Federation
image:../{{book.images}}/user-federation-page.png[]
You will now be able to login 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
login.
=== Advanced Configuration
Our `PropertyFileUserStorageProvider` example is bit contrived. It is hardcoded to a property file that is embedded
in the jar of the provider. Not very useful at all. We may want to make the location of this file configurable per
instance of the provider. In other words, we may want to re-use this provider mulitple times in multiple different realms
and point to completely different user property files. We'll also want to do this configuration within the admin
console UI.
The `UserStorageProviderFactory` has additional methods you can implement that deal with provider configuration.
You describe the variables you want to configure per provider and the admin console will automatically render
a generic input page to gather this configuration. There's also callback methods to validate 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]
----
List<ProviderConfigProperty> getConfigProperties();
default
void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException
{
}
default
void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
}
default
void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel model) {
}
----
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
Let's expand our `PropertyFileUserStorageProviderFactory` example to allow you to to point a provider instance to a specific
file on disk.
.PropertyFileUserStorageProviderFactory
[source,java]
----
public class PropertyFileUserStorageProviderFactory implements UserStorageProviderFactory<PropertyFileUserStorageProvider> {
protected static final List<ProviderConfigProperty> configMetadata;
static {
configMetadata = ProviderConfigurationBuilder.create()
.property().name("path")
.type(ProviderConfigProperty.STRING_TYPE)
.label("Path")
.defaultValue("${jboss.server.config.dir}/example-users.properties")
.helpText("File path to properties file")
.default
.add().build();
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configMetadata;
}
----
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. In the admin console config page for this provider,
this config variable will be labed as `Path` and have a default value of `${jboss.server.config.dir}/example-users.properties`.
When you hover over the tooltip of this config option, it will display 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 don't 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.
[source,java]
----
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config)
throws ComponentValidationException {
String fp = config.getConfig().getFirst("path");
if (fp == null) throw new ComponentValidationException("user property file does not exist");
fp = EnvUtil.replace(fp);
File file = new File(fp);
if (!file.exists()) {
throw new ComponentValidationException("user property file does not exist");
}
}
----
In the `validateConfiguration()` method we get the config variable from the `ComponentModel` and we check to see
if that file exists on disk. Notice that we use the `org.keycloak.common.util.EnvUtil.replace()`method. With this method
any string that has `${}` within it will replace that with a system property value. The `${jboss.server.config.dir}`
string corresponds to the `configuration/` directory of our server and is really useful for this example.
Next thing we have to do is remove the old `init()` method. We do this because user property files are going to be
unique per provider instance. We move this logic to the `create()` method.
[source,java]
----
@Override
public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) {
String path = model.getConfig().getFirst("path");
Properties props = new Properties();
try {
InputStream is = new FileInputStream(path);
props.load(is);
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return new PropertyFileUserStorageProvider(session, model, props);
}
----
This logic, is of course, is really inefficient as every different transaction will read the entire user property file from disk,
but hopefully this illustrates, in a simple way, how to hook in configuration variables.
==== Configure in Admin Console
Now that the configuration is enabled, you can set the `path` variable when you configure the provider in the admin console.
.Configured Provider
image:../{{book.images}}/storage-provider-with-config.png[]