Chapter 7. Identity Management - Configuration
- 7.1. Configuration
- 7.1.1. Architectural Overview
- 7.1.2. Default Configuration
- 7.1.3. Providing a Custom Configuration
- 7.1.4. Initializing the
PartitionManager
- 7.1.5. Programmatic Configuration Overview
- 7.1.6. Providing Multiple Configurations
- 7.1.7. Providing Multiple Stores for a Configuration
- 7.1.8. Configuring Credential Handlers
- 7.1.9. Identity Context Configuration
- 7.1.10. IDM configuration from XML file
7.1. Configuration
7.1.1. Architectural Overview
Configuration in PicketLink is in essence quite simple; an
IdentityConfiguration
object must first be created to hold the PicketLink configuration options. Once all configuration options have been set, you just create a PartitionManager
instance passing the previously created configuration. The PartitionManager
can then be used to create Partition
and IdentityManager
instances.
The
IdentityConfiguration
is usually created using a Configuration Builder API, which provides a rich and fluent API for every single aspect of PicketLink configuration.
7.1.2. Default Configuration
If you'd like to get up and running with IDM quickly, the good news is that PicketLink will provide a default configuration that stores your identity data on the file system if no other configuration is available. This means that if you have the PicketLink libraries in your project, you can simply inject the
PartitionManager
, IdentityManager
or RelationshipManager
beans into your own application and start using them immediately:
@Inject PartitionManager partitionManager; @Inject IdentityManager identityManager; @Inject RelationshipManager relationshipManager;
Note
The default configuration is very useful for developing and testing purposes, as you don't need a database or a LDAP server to start managing your identity data.
7.1.3. Providing a Custom Configuration
In certain cases the default configuration may not be enough to your application. You can easily provide your own configuration by observing a specific
IdentityConfigurationEvent
:
public class IdentityManagementConfiguration { public void observeIdentityConfigurationEvent(@Observes IdentityConfigurationEvent event) { IdentityConfigurationBuilder builder = event.getConfig(); // use the builder to provide your own configuration } }
You can also provide your own configuration by producing one or more
IdentityConfiguration
instances using a @Producer
annotated method:
public class IdentityManagementConfiguration { @Produces public IdentityConfiguration produceJPAConfiguration() { IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("jpa.config") .stores() .jpa() .supportAllFeatures() return builder.build(); } @Produces public IdentityConfiguration produceLDAPConfiguration() { IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("ldap.config") .stores() .ldap() // configure the LDAP store return builder.build(); } }
The example above produces two distinct configurations: one using a JPA store and another using the LDAP store. During the startup PicketLink will resolve both configurations and initialize the IDM subsystem with them. You can also provide a single configuration.
Warning
When producing
IdentityConfiguration
instances the produced bean must be dependent and not normal-scoped.
For last, you can also build your own
PartitionManager
instance if you want more control.
@ApplicationScoped public static class PicketLinkConfiguration { @PicketLink @Produces public PartitionManager producePartitionManager() { IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("produced.partition.manager.config") .stores() .jpa() .supportAllFeatures(); PartitionManager partitionManager = new DefaultPartitionManager(builder.build()); // in this case you must initialize the identity store with a default partition Partition defaultPartition = new Realm(Realm.DEFAULT_REALM); partitionManager.add(defaultPartition); // creates the default partition return partitionManager; } }
The example above allows you to produce your own
PartitionManager
instance. Note that the producer method is annotated with the PicketLink
annotation.
Important
When producing your own
PartitionManager
is that you must manually create the partitions before start producing IdentityManager
instances (eg.: the default partition)
For last, you can also provide the configuration by observing the
SecurityConfigurationEvent
as follows:
public class IdentityManagementConfiguration { public void configureIdentityManagement(@Observes SecurityConfigurationEvent event) { SecurityConfigurationBuilder builder = event.getBuilder(); builder .idmConfig() .named("default.config") .stores() .jpa() .supportAllFeatures(); } }
You may be asking which approach is recommended, right ? We always recommend to use the last approach, observe the
SecurityConfigurationEvent
. The reason is because from this event you have access to not only the IDM config but for others features provided by PicketLink.
However you may want to provide your own
PartitionManager
instance, instead of relying on PicketLink to create one for you. In this case you can produce a PartitionManager
by your own. Or if you want different methods for each IDM config you may want to produce IdentityConfiguration
instances instead.
7.1.4. Initializing the PartitionManager
You may need to initialize the
PartitionManager
with some data before your application starts to produce partition manager instances. PicketLink provides a specific event called PartitionManagerCreateEvent
, which can be used to provide any initialization logic right after a PartitionManager
instance is created and before it is consumed by any injection point in your application.
public class MyPartitionManagerInitializer { public void init(@Observes PartitionManagerCreateEvent event) { // retrieve the recently created partition manager instance PartitionManager partitionManager = event.getPartitionManager(); // retrieve all the configuration used to build the instance Collection configurations = partitionManager.getConfigurations(); } }
One important thing to keep in mind when providing a observer for
PartitionManagerCreateEvent
is that if any partition is created during the initialization, PicketLink won't try to create the default partition.
Note
Apache TomEE users should always provide an observer for
PartitionManagerCreateEvent
in order to initialize the partition manager properly. Specially if using the JPA store and if no active transaction exists when the PartitionManager
is being created.
7.1.5. Programmatic Configuration Overview
The Identity Management configuration can be defined programmatically using the Configuration Builder API. The aim of this API is to make it easier to chain coding of configuration options in order to speed up the coding itself and make the configuration more readable.
Let's assume that you want to quick start with PicketLink Identity Management features using a File-based Identity Store. First, a fresh instance of
IdentityConfiguration
is created using the IdentityConfigurationBuilder
helper object, from where we choose which identity store we want to use (in this case a file-based store) and any other configuration option, if necessary. Finally, we use the configuration to create a PartitionManager
, from where we can create Partition
and IdentityManager
instances:
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("default") .stores() .file() .supportAllFeatures(); DefaultPartitionManager partitionManager = new DefaultPartitionManager(builder.buildAll()); Realm defaultRealm = new Realm(Realm.DEFAULT_REALM); // let's add the partition using the default configuration. partitionManager.add(defaultRealm); // if no partition is specified to createIdentityManager, defaults to the default Realm. IdentityManager identityManager = partitionManager.createIdentityManager(); User john = new User("john"); // let's add john to the default partition identityManager.add(user);
7.1.6. Providing Multiple Configurations
A
PartitionManager
can be built considering multiple configurations. This is a very powerful feature given that you can manage your identity data between different partitions each one using a specific configuration.
As discussed before, each configuration has a name. The name can be used to identify a configuration set as well to tell PicketLink the configuration that should be used to manage a specific
Partition
.
Let's take a more close look how you can use multiple configurations:
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("ldap.config") .stores() .ldap() // specific configuration options for the LDAP store .supportAllFeatures(); .named("jpa.config") .stores() .jpa() // specific configuration options for the JPA store .supportAllFeatures(); DefaultPartitionManager partitionManager = new DefaultPartitionManager(builder.buildAll()); Realm internalPartition = new Realm("internal"); // the 'internal' partition will use the 'ldap.config' configuration partitionManager.add(internalPartition, "ldap.config"); // we create an IdentityManager for the LDAP managed partition IdentityManager internalIdentityManager = partitionManager.createIdentityManager(internalPartition); User john = new User("john"); // john will be added to the LDAP internalIdentityManager.add(john); Realm externalPartition = new Realm("external"); // the 'external' partition will use the 'jpa.config' configuration partitionManager.add(externalPartition, "jpa.config"); User mary = new User("mary"); // we create an IdentityManager for the JPA managed partition IdentityManager externalIdentityManager = partitionManager.createIdentityManager(externalPartition); // mary will be added to the database externalIdentityManager.add(mary);
The example above is just one of the different things you can do with PicketLink. The code above defines two partitions: one for internal users and another one for external users. Each partition is associated with one of the provided configurations where the internal partition will use LDAP to store users whether the external partition will use JPA.
When you create a
IdentityManager
for one of those partitions, all identity management operations will be done considering the configuration associated with the current partition. In other words, considering the example above, the user 'john' will be stored in the LDAP and 'mary' in a Database.
7.1.7. Providing Multiple Stores for a Configuration
It is also possible to use multiple
IdentityStore
configurations in a single named configuration. This can be very useful when your identity data is distributed in different stores or even if a specific store have any kind of limitation that can be provided by another one.
For instance, the LDAP store have some limitations and does not support all features provided by PicketLink. One of those unsupported features is the ability to handle ad-hoc attributes. When using LDAP you're tied with a schema that usually is very hard to change in order to support all your needs.
In this cases, PicketLink allows you to combine in a single configuration the LDAP and the JPA store, for example. Where you can use LDAP for users, roles and groups and use the JPA store for relationships.
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("default") .stores() .jpa() // configuration options for the jpa store .supportGlobalRelationship(Relationship.class) .supportAttributes(true) .ldap() // configuration options for the ldap store .supportType(IdentityType.class)
The example above defines a single configuration with two stores: LDAP and JPA. For the LDAP store configuration we define that only
IdentityType
types should be supported. In other words, we're only storing users, roles and groups. For the JPA store configuration we define that only Relationship
types should be supported. In other words, we're only storing relationships such as Grant
, GroupMembership
, etc.
You may also notice that the JPA store is configured to support attributes too. What means that we can now use ad-hoc attributes for all the supported types.
7.1.8. Configuring Credential Handlers
Each
IdentityStore
may support a different set of credential handlers. This documentations describes the built-in credential handlers provided by PicketLink, but sometimes you may want to provide your own implementations.
When you write your custom credential handler you need to tell PicketLink the identity store that will support it. This is done by the following code:
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("default") .stores() .jpa() // other JPA configuration .addCredentialHandler(UserPasswordCredentialHandler.class) .supportAllFeatures();
The example above shows how to configure a credential handler for a JPA-based store using the
addCredentialHandler
method.
7.1.8.1. Passing parameters to Credential Handlers
Some credential handlers support a set of configuration options to configure their behavior. These options can be specified as follows:
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("default") .stores() .jpa() // other JPA configuration .setCredentialHandlerProperty(PasswordCredentialHandler.PASSWORD_ENCODER, new BCryptPasswordEncoder(4)) .supportAllFeatures();
The example above shows how to set a property for the
PasswordCredentialHandler
using the setCredentialHandlerProperty
method.
7.1.9. Identity Context Configuration
The
IdentityContext
plays an important role in the PicketLink IDM achitecture. It is strongly used during the execution of operations. It carries very sensitive and contextual information for a specific operation and provides access for some of the IDM underlying services such as caching, event handling, id generator for AttributedType
instances, among others.
Operations are always executed by a specific
IdentityStore
in order to persist or store identity data using a specific repository (eg.: LDAP, databases, filesystem, etc). When executing a operation the identity store must be able to:
-
Access the current
Partition
. Eg.:Realm
orTier
. -
Access the Event Handling API in order to fire events such as when an user is created, updated, etc.
-
Access the Caching API in order to cache identity data and increase performance.
-
Access to external resources, provided before the operation is executed and initialized by a
ContextInitializer
.
7.1.9.1. Initializing the IdentityContext
Sometimes you may need to provide additional configuration or even references for external resources before the operation is executed by an identity store. An example is how you tell to the
JPAIdentityStore
which EntityManager
instance should be used. When executing an operation, the JPAIdentityStore
must be able to access the current EntityManager
to persist or retrieve data from the database. You need someway to populate the IdentityContext
with such information. When you're configuring an identity store, there is a configuration option that allows you to provide a ContextInitializer
implementation.
public interface ContextInitializer { void initContextForStore(IdentityContext context, IdentityStore<?> store); }
The method
initContextForStore
will be invoked for every single operation and before its execution by the identity store. It can be implemented to provide all the necessary logic to initialize and populate the IdentityContext
for a specific IdentityStore
.
The configuration is also very simple:
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named("default") .stores() .file() .supportAllFeatures(); .addContextInitializer(new MyContextInitializer());
You can provide multiple initializers.
Note
Remember that initializers are executed for every single operation. Also, the same instance is used between operations which means your implementation should be “stateless”. You should be careful about the implementation in order to not impact performance, concurrency or introduce unexpected behaviors.
7.1.10. IDM configuration from XML file
Actually it's possible to configure IDM with XML configuration. This possibility is good especially in case when you want Picketlink IDM to be part of bigger system and your users won't have a possibility to change source code and so they can't configure it programmatically with the Builder API. So they will just need to change the configuration in XML file instead of doing some changes directly in source code.
7.1.10.1. Unified XML configuration
Whole Picketlink project provides unified format of configuration file, so that you can configure Section 14.1, “Overview” and IDM in same file.
<PicketLink xmlns="urn:picketlink:identity-federation:config:2.1"> <PicketLinkIDP xmlns="urn:picketlink:identity-federation:config:1.0" ServerEnvironment="tomcat" BindingType="POST" SupportsSignatures="true"> <!-- SAML2 IDP configuration is here --> </PicketLinkIDP> <Handlers xmlns="urn:picketlink:identity-federation:handler:config:2.1"> <!-- Configuration of SAML2 handlers is here --> </Handlers> <PicketLinkSTS xmlns="urn:picketlink:identity-federation:config:1.0" STSName="Test STS" TokenTimeout="7200" EncryptToken="true"> <!-- Configuration of Picketlink STS is here --> </PicketlinkSTS> <PicketLinkIDM> <!-- IDM configuration is here --> </PicketLinkIDM> </PicketLink>
Note that if you don't want to use Picketlink Federation, you can omit it's configuration and use just IDM.
7.1.10.2. XML configuration format
XML configuration is leveraging Section 7.1.5, “Programmatic Configuration Overview” and Java reflection during it's parsing, so names of XML elements are actually same like names of particular Builder methods.
For example, let's assume that you want to use
FileIdentityStore
and your programmatic configuration looks like this:
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder(); builder .named(SIMPLE_FILE_STORE_CONFIG) .stores() .file() .preserveState(false) .supportGlobalRelationship(Relationship.class) .supportAllFeatures();
Same XML configuration to configure IDM with
FileIdentityStore
will look like this:
<PicketLink xmlns="urn:picketlink:identity-federation:config:2.1"> <PicketLinkIDM> <named value="SIMPLE_FILE_STORE_CONFIG"> <stores> <file> <preserveState value="false" /> <supportGlobalRelationship value="org.picketlink.idm.model.Relationship" /> <supportAllFeatures /> </file> </stores> </named> </PicketLinkIDM> </PicketLink>
You can take a look at testsuite to see more examples.
7.1.10.3. Bootstrap IDM from XML file
So to initialize Picketlink IDM from XML you can use the code like this:
// Replace with your own configuration file String configFilePath = "config/embedded-file-config.xml"; ClassLoader tcl = Thread.currentThread().getContextClassLoader(); InputStream configStream = tcl.getResourceAsStream(configFilePath); XMLConfigurationProvider xmlConfigurationProvider = new XMLConfigurationProvider(); IdentityConfigurationBuilder idmConfigBuilder = xmlConfigurationProvider.readIDMConfiguration(configStream);
Now you can bootstrap IDM from
idmConfigBuilder
in same way, like it's done in Section 7.1.5, “Programmatic Configuration Overview” . Note that you can initialize builder from XML file and then you can do some additional programmatic configuration. For example, you may need to programmatically add JPAContextInitializer
in case that you are using JPA, because you will need access to JPA EntityManager
.