Merge pull request #18 from patriot1burke/master

more user storage
This commit is contained in:
Bill Burke 2016-12-03 11:27:46 -05:00 committed by GitHub
commit b652487ef0
5 changed files with 209 additions and 4 deletions

View file

@ -22,3 +22,4 @@
.. link:topics/user-storage/import.adoc[Import Implementation Strategy]
.. link:topics/user-storage/cache.adoc[User Caches]
.. link:topics/user-storage/javaee.adoc[Leveraging Java EE]
.. link:topics/user-storage/rest.adoc[REST Management API]

View file

@ -175,12 +175,29 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
=== Registering provider implementations
Keycloak can load provider implementations from JBoss Modules or directly from the file-system.
There are two ways to register provider implementations. The easiest way is to just throw your provider jar within
the Keycloak `deploy/` directory. Keycloak supports hot deployment as well in this scenario. This is also the best
solution.
The alternative is not really recommended, but exists for legacy purposes as the Keycloak deployer didn't exist in
previous versions of the project. Keycloak can load provider implementations from JBoss Modules or directly from the file-system.
Using Modules is recommended as you can control exactly what classes are available to your provider.
Any providers loaded from the file-system uses a classloader with the Keycloak classloader as its parent.
==== Using the Keycloak Deployer
If you throw your provider jar within the Keycloak `deploy/` directory, your provider will automatically be deployed.
Hot deployment works too. Additionally, your provider jar works similarly to other components deployed in a JBoss/Wildfly
environment in that they can use facilities like the `jboss-deployment-structure.xml` file. This file allows you to
set up dependencies on other components and load third-party jars and modules.
Provider jars can also be contained within other deployable units like EARs and WARs. Deploying with a EAR actually makes
it really easy to use third party jars as you can just put these libraries in the EAR's `lib/` directory.
==== Register a provider using Modules
WARNING: We don't recommend this approach.
To register a provider using Modules first create a module.
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`.
For example to add the event listener sysout example provider using the `jboss-cli` script execute:
@ -277,6 +294,82 @@ For example to disable the Infinispan user cache provider add:
</spi>
----
=== Leveraging Java EE
The can be packaged within any Java EE component so long as you set up the `META-INF/services`
file correctly to point to your providers. For example, if your provider needs to use third party libraries, you
can package up your provider within an ear and store these third pary libraries in the ear's `lib/` directory.
Also note that provider jars can make use of the `jboss-deployment-structure.xml` file that EJBs, WARS, and EARs
can use in a JBoss/Wildfly environment. See the JBoss/Wildfly documentation for more details on this file. It
allows you to pull in external dependencies among other fine grain actions.
`ProviderFactory` implementations are required to be plain java objects. But, we also currently support
implementing provider classes as Stateful EJBs. TThis is how you would do it:
[source,java]
----
@Stateful
@Local(EjbExampleUserStorageProvider.class)
public class EjbExampleUserStorageProvider implements UserStorageProvider,
UserLookupProvider,
UserRegistrationProvider,
UserQueryProvider,
CredentialInputUpdater,
CredentialInputValidator,
OnUserCache
{
@PersistenceContext
protected EntityManager em;
protected ComponentModel model;
protected KeycloakSession session;
public void setModel(ComponentModel model) {
this.model = model;
}
public void setSession(KeycloakSession session) {
this.session = session;
}
@Remove
@Override
public void close() {
}
...
}
----
You have to 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.
You must 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.
Implementations 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.
[source,java]
----
public class EjbExampleUserStorageProviderFactory
implements UserStorageProviderFactory<EjbExampleUserStorageProvider> {
@Override
public EjbExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) {
try {
InitialContext ctx = new InitialContext();
EjbExampleUserStorageProvider provider = (EjbExampleUserStorageProvider)ctx.lookup(
"java:global/user-storage-jpa-example/" + EjbExampleUserStorageProvider.class.getSimpleName());
provider.setModel(model);
provider.setSession(session);
return provider;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
----
=== Available SPIs
Here's a list of the most important available SPIs and a brief description. For more details on each SPI refer to individual sections.

View file

@ -69,6 +69,11 @@ For your providers, you can use this to _intercept_ other methods on the local `
with your extern store. For example, get methods could make sure that the local store is in sync. Set methods
keep external store in sync with local one.
NOTE: If your provider is implementing the `UserRegistrationProvider` interface, your `removeUser()` method does not
need to remove the user from local storage. The runtime will automatically perform this operation. Also
note that `removeUser()` will be invoked before it is removed from local storage.
==== ImportedUserValidation Interface
If you remember earlier in this chapter, we discussed how querying for a user worked. Local storage is queried first,

View file

@ -56,6 +56,10 @@ Notice that when adding a user we set the password value of the property map to
we can't have null values for a property in the property value. We also have to modify the `CredentialInputValidator`
methods to reflect this.
`addUser()` will be called if the provider implements the `UserRegistrationProvider` interface. If your provider has
a configuration switch to turn of adding a user, returning `null` from this method will skip the provider and call
the next one.
.PropertyFileUserStorageProvider
[source,java]
----

View file

@ -0,0 +1,102 @@
=== REST Management API
You can create, remove, and update your user storage provider deployments through the admin 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.
The REST Component API lives under your realm admin resource.
----
/admin/realms/{realm-name}/components
----
We will only show this REST API interaction with the Java client. Hopefully you can extract how do do this from
curl from this api.
[source,java]
----
public interface ComponentsResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<ComponentRepresentation> query();
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<ComponentRepresentation> query(@QueryParam("parent") String parent);
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<ComponentRepresentation> query(@QueryParam("parent") String parent, @QueryParam("type") String type);
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<ComponentRepresentation> query(@QueryParam("parent") String parent,
@QueryParam("type") String type,
@QueryParam("name") String name);
@POST
@Consumes(MediaType.APPLICATION_JSON)
Response add(ComponentRepresentation rep);
@Path("{id}")
ComponentResource component(@PathParam("id") String id);
}
public interface ComponentResource {
@GET
public ComponentRepresentation toRepresentation();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public void update(ComponentRepresentation rep);
@DELETE
public void remove();
}
----
To create a user storage provider, you must specify the provider id, a provider type of the string `org.keycloak.storage.UserStorageProvider`,
as well as the configuration.
[source,java]
----
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.RealmRepresentation;
...
Keycloak keycloak = Keycloak.getInstance(
"http://localhost:8080/auth",
"master",
"admin",
"password",
"admin-cli");
RealmResource realmResource = keycloak.realm("master");
RealmRepresentation realm = realmResource.toRepresentation();
ComponentRepresentation component = new ComponentRepresentation();
component.setName("home");
component.setProviderId("readonly-property-file");
component.setProviderType("org.keycloak.storage.UserStorageProvider");
component.setParentId(realm.getId());
component.setConfig(new MultivaluedHashMap());
component.getConfig().putSingle("path", "~/users.properties");
realmResource.components().add(component);
// retrieve a component
List<ComponentRepresentation> components = realmResource.components().query(realm.getId(),
"org.keycloak.storage.UserStorageProvider",
"home");
component = components.get(0);
// Update a component
component.getConfig().putSingle("path", "~/my-users.properties");
realmResource.components().component(component.getId()).update(component);
// Remove a component
realmREsource.components().component(component.getId()).remove();
----