This site contains the documentation that is relevant to older WSO2 product versions and offerings.
For the latest WSO2 documentation, visit https://wso2.com/documentation/.
Writing a Custom User Store Manager (V4)
AbstractUserStoreManager and implementations
There are a set of methods available in the AbstractUserStoreManager
 class. These methods are used when interacting with user stores. When we implement a custom user store manager, it is important to identify the methods that must be implemented or overridden.Â
Tip: Decide on which methods you want to use based on what you want to do. For example, if you want to change the way you authorize users, you only need to implement the relevant method for that purpose. if you want to integrate with a completely different datasource by extending the AbstractUserStoreManager
class in read/write mode, you must implement all the methods. If your user store is a read-only user store, you must implement the important methods and read methods.
The following list briefly explains the use of the available methods in the AbstractUserStoreManager
 class. Most of the methods provide a configuration option through properties. It is recommended to use these methods with the provided customization options.
Important methods
Available methods | Default behaviour | Reasons for overriding |
---|---|---|
| This method returns details on whether the given username and password is matched or not. Credential is usually a String literal. | If you want to change the authentication logic you can override this method and write your own implementation. The default task of this method is to compare the given password with the stored password. The given credentials are passed to the |
| This returns the encrypted or plain-text password based on the configurations. | You can override this method if you need to change the way you encrypt the password. If you want to change the algorithm that is used for encryption, you can configure it. |
| The default properties of the user store are returned using this method. These properties are used in user store related operations. Be sure to manually add the following property when you implement the class: setOptionalProperty("Disabled", "false", "Whether user store is disabled"); This property is what controls whether the user store is enabled or disabled. | By overriding this method, you can programmatically change the configuration of the user store manager implementation. |
| Returns whether the given username is compatible with the defined criteria. | The criteria used for defining a valid username can be configured as a regex in user store configurations. If you want to change the way user name validation is done, override this method. |
| Returns whether the given password is compatible with the defined criteria. This is invoked when creating a user, updating a password and authorization. | Similar to the user name, you can configure the format of a valid password in configuration. If you want to change that behavior you can override this method. |
Read-write methods
Available methods | Default behaviour |
---|---|
| This method is responsible to create a new user based on the given values. We can change the JDBC query or LDAP attribute name with the user store configuration. |
| This removes the user store record related to the given username. |
| Responsible to update the credential of the given username after authenticating with the existing password. |
| Admin users can use this method to update the credentials of a given user. This can be done without validating the existing password. |
| Creates a new user role with given roleName and maps the given users to newly created role. Shared parameter indicate whether this role is shared among tenant or not. |
| This method removes the given role and related mappings from the user store. |
| This method is used to update the name of the existing roles. |
| This is used to delete the existing mappings between the given user and the |
| Used to delete the existing mappings between the given role and the |
| This is responsible for creating a new claim for a given user and profile, with the given claim URI and value. |
| This is responsible for creating a new claim for a given user and profile, with the given list of claim URIs and values. |
| Remove the existing claim details mapped with the given user and profile. |
| Remove the given list of claims from a given user. |
| This method is used to persist tokens in the user store. |
Read methods
Available methods | Default behaviour |
---|---|
| Returns whether the provided userName already exists in the user store. |
| Returns whether the provided roleName already exists in the user store. |
| This method returns a list of usernames that match with the given filter string. |
| Returns a list of role names that match with the given filter string. |
| Returns a list of external role names of a given user that match with the given filter string. |
| This method returns a list of shared role names of a given user that match with the given filter string. |
| This method returns values for the given propertyNames for a given userName and profileName . |
| This returns a list of usernames that match with the given value of the given property and profileName . |
| Returns names to display in the UI for given usernames. |
| Returns the password expiry date of a given user. The default value is null. |
| This method returns the identifier of a given user name. Default value is 0. |
boolean doCheckIsUserInRole(String userName, String roleName) | True is returned if the given user is already mapped to the given role name. |
String[] getProfileNames(String userName) | Returns a list of profile names mapped with a given user name. |
| This returns a list of role names that are associated with the given tenant domain and match with the filter. |
| This method returns a list of usernames that are mapped with the given rolename. |
| All the profile names are returned including the default profile. |
| This method is used to check if the given token exists for the given user. |
| Returns whether this user store is allowed to have multiple profiles per user. The default value is false . |
| This method returns whether this user store allows bulk transactions or not. |
About overriding methods
You must select the methods to override based on your requirement. For example, if you want to change the way you encrypt the password, you only need to implement the preparePassword
method. If you want to implement a completely new read/write user store manager, you must implement all the methods listed in the above tables. If the user store is read-only, you can implement only the important methods and read methods (if you extend from AbstractUserStoreManager
you have to keep unrelated methods empty).
There are a few other methods used for internal purposes. You do not need to override those methods.
Implementations
In WSO2 Carbon-based products, there are four user store manager classes that implement the AbstractUserStoreManager
 class. You can select one of those classes according to the user store that you have in your environment.
User store manager class | When you would use it |
---|---|
org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager | If your user details are stored in a database, you must use this user store manager implementation. With the abstraction provided in this implementation, most of the JDBC based scenarios can be handled without writing a custom user store manager. |
org.wso2.carbon.user.core.ldap.ReadOnlyLDAPUserStoreManager | You can use this class if you have an LDAP user store. This implementation does not allow you to insert or update users from the WSO2 product side. Instead you can only read and use them in the product. |
org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager | If you want to allow the WSO2 product to manipulate user store data, you need to use this implementation. |
org.wso2.carbon.user.core.ldap.ActiveDirectoryLDAPUserStoreManager | Active Directory also can be used as the user store of a WSO2 product and you can configure it using this user store manager implementation. |
Implementing a custom JDBC user store manager
The instructions in this section are focused on implementing a sample JDBC user store manager. The following topics guide you through setting up the implementation, and writing, deploying and configuring a custom user store manager for two sample scenarios. You can decide on which scenario to follow based on your requirement.
For this sample, the following tools are used to implement the custom user store manager.
- Java 1.7.0
- IDE (Eclipse, InteliJ IDEA, etc.)
- Apache Maven
Setting up the implementation
To set up this implementation, do the following.
- Create a new Apache Maven project with the help of the IDE that you are using. The project should be a simple Apache Maven project and you can use any desired artifact and group ID.Â
Add the WSO2 user store management .jar file as a dependency of our project. Since this .jar file is stored in WSO2's Maven repository, you must add the WSO2 repository to your POM file. Please see the below sample POM file.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <artifactId>custom-userstore</artifactId> <groupId>com.wso2.custom</groupId> <version>1.0-SNAPSHOT</version> <modelVersion>4.0.0</modelVersion> <dependencies> <dependency> <groupId>org.wso2.carbon</groupId> <artifactId>org.wso2.carbon.user.core</artifactId> <version>4.2.0</version> </dependency> </dependencies> <repositories> <repository> <id>wso2-nexus</id> <name>WSO2 internal Repository</name> <url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url> <releases> <enabled>true</enabled> <updatePolicy>daily</updatePolicy> <checksumPolicy>fail</checksumPolicy> </releases> </repository> </repositories> </project>
Now your basic implementation is ready.
Two sample scenarios have been demonstrated below for writing and deploying the custom user store manager. Follow the one most suited to your requirement.Â
Sample scenario: custom user store manager using a third party library
As a sample of how this can be done, consider a scenario where you want to use a custom hashing method using a 3rd party library such as Jasypt. In order to do this, you must override the doAuthentication()
 and preparePassword()
 methods as an example. Follow the sections below to write, deploy and configure the custom user store manager for this scenario.Â
Writing the custom user store manager
Do the following steps to write the custom user store manager.
Include the required dependencies in your development environment. To do that, include the relevant Apache Maven dependency details or manually add the .jar files to your classpath. For example, add the following XML snippet under the dependencies tag in your pom.xml file to include the Jasypt dependency.
<dependency> <groupId>org.jasypt</groupId> <artifactId>jasypt</artifactId> <version>1.9.2</version> </dependency>
Create a new class by extending the existingÂ
JDBCUserStoreManager
 implementation. The following code is an example of how this would look.package com.wso2.custom.usermgt; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasypt.util.password.StrongPasswordEncryptor; import org.wso2.carbon.user.api.RealmConfiguration; import org.wso2.carbon.user.core.UserRealm; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.claim.ClaimManager; import org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager; import org.wso2.carbon.user.core.profile.ProfileConfigurationManager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Date; import java.util.GregorianCalendar; import java.util.Map; public class CustomUserStoreManager extends JDBCUserStoreManager { private static Log log = LogFactory.getLog(StarkUserStoreManager.class); // This instance is used to generate the hash values private static StrongPasswordEncryptor passwordEncryptor = new StrongPasswordEncryptor(); // You must implement at least one constructor public CustomUserStoreManager(RealmConfiguration realmConfig, Map<String, Object> properties, ClaimManager claimManager, ProfileConfigurationManager profileManager, UserRealm realm, Integer tenantId) throws UserStoreException { super(realmConfig, properties, claimManager, profileManager, realm, tenantId); log.info("CustomUserStoreManager initialized..."); } @Override public boolean doAuthenticate(String userName, Object credential) throws UserStoreException { boolean isAuthenticated = false; if (userName != null && credential != null) { try { String candidatePassword = (String) credential; Connection dbConnection = null; ResultSet rs = null; PreparedStatement prepStmt = null; String sql = null; dbConnection = this.getDBConnection(); dbConnection.setAutoCommit(false); // get the SQL statement used to select user details sql = this.realmConfig.getUserStoreProperty("SelectUserSQL"); if (log.isDebugEnabled()) { log.debug(sql); } prepStmt = dbConnection.prepareStatement(sql); prepStmt.setString(1, userName); // check whether tenant id is used if (sql.contains("UM_TENANT_ID")) { prepStmt.setInt(2, this.tenantId); } rs = prepStmt.executeQuery(); if (rs.next()) { String storedPassword = rs.getString(3); // check whether password is expired or not boolean requireChange = rs.getBoolean(5); Timestamp changedTime = rs.getTimestamp(6); GregorianCalendar gc = new GregorianCalendar(); gc.add(GregorianCalendar.HOUR, -24); Date date = gc.getTime(); if (!(requireChange && changedTime.before(date))) { // compare the given password with stored password using jasypt isAuthenticated = passwordEncryptor.checkPassword(candidatePassword, storedPassword); } } log.info(userName + " is authenticated? " + isAuthenticated); } catch (SQLException exp) { log.error("Error occurred while retrieving user authentication info.", exp); throw new UserStoreException("Authentication Failure"); } } return isAuthenticated; } @Override protected String preparePassword(String password, String saltValue) throws UserStoreException { if (password != null) { // ignore saltValue for the time being log.info("Generating hash value using jasypt..."); return passwordEncryptor.encryptPassword(password); } else { log.error("Password cannot be null"); throw new UserStoreException("Authentication Failure"); } } }
Note: The default constructor is not enough when you implement a custom user store manager, and you must implement a constructor with relevant arguments.
Deploying and configuring the custom user store manager
Do the following to deploy and configure the custom user store manager in your WSO2 product.
- Copy the artifact of your project (custom-userstore.jar, in this case) to theÂ
<PRODUCT_HOME>/repository/components/dropins
 directory. Also copy all OSGI bundles to this location. If you have any dependency .jar files, copy them to theÂ<PRODUCT_HOME>/repository/components/lib
 directory. Change the configuration of the WSO2 product to use our custom implementation for user store management. To do this, open theÂ
<PRODUCT_HOME>/repository/conf/user-mgt.xml
 file and change theÂUserStoreManager
 class.<UserStoreManager class="com.wso2.custom.usermgt.CustomUserStoreManager">
You do not need to change anything else since you extend the JDBCUserStoreManager class, so the configurations will remain the same.
Tip: This step provides instructions on configuring your custom user store manager as a primary user store manager. Alternatively, you can configure this as a secondary user store if you already have a different primary user store configured. To use it as a secondary user store manager, you have to register it to the OSGI framework. See Sample scenario: secondary custom user store manager for more information.
You have now implemented a custom user store manager for a WSO2 product. Once you have done this, start the product and see the log messages that you have placed inside overridden methods when you create a new user or login. This ensures that all your configurations work as intended.
Sample scenario: secondary custom user store manager for an existing database
Consider a scenario where your company already has a user database and needs to authenticate to a WSO2 product through that database.  The following instructions demonstrate how to implement this sample scenario by writing a secondary custom user store manager with a WSO2 product.  Follow the sections below to write, deploy and configure the custom user store manager for this scenario.Â
Writing a secondary custom user store manager
Do the following to write a secondary custom user store manager. The sample code can be found here.Â
The following code creates a schema of a sample user database.Â
CREATE TABLE TEST_USER ( USER_ID INT NOT NULL PRIMARY KEY, USER_NAME VARCHAR(100), ENCRYPTED_USER_PASSWORD VARCHAR(100), EMAIL_ADDRESS VARCHAR(240), EMPLOYEE_ID INT ); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (1, "testadmin", "testpass", "admin@act.org", 1000); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (2, "user1", "user1", "user1@act.org", 1001); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (3, "user2", "user2", "user2@act.org", 1002); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (4, "user3", "user3", "user3@act.org", 1003); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (5, "user4", "user4", "user4@act.org", 1004); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (6, "user5", "user5", "user5@act.org", 1005); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (7, "user6", "user6", "user6@act.org", 1006); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (8, "user7", "user7", "user7@act.org", 1007); INSERT INTO TEST_USER (USER_ID, USER_NAME, ENCRYPTED_USER_PASSWORD, EMAIL_ADDRESS, EMPLOYEE_ID) VALUES (9, "user8", "user8", "user8@act.org", 1008);
The custom user store in this scenario is a JDBC based user store so you can write it by extending theÂ
o
rg.wso2.carbon.user.core.jdbc.JDBCUserStoreManagerÂ
class. First, override theÂdoAuthenticate()
 method to authenticate the new database as follows.Â@Override public boolean doAuthenticate(String userName, Object credential) throws UserStoreException { if (CarbonConstants.REGISTRY_ANONNYMOUS_USERNAME.equals(userName)) { log.error("Anonymous user trying to login"); return false; } Connection dbConnection = null; ResultSet rs = null; PreparedStatement prepStmt = null; String sqlstmt = null; String password = (String) credential; boolean isAuthed = false; try { dbConnection = getDBConnection(); dbConnection.setAutoCommit(false); sqlstmt = realmConfig.getUserStoreProperty(JDBCRealmConstants.SELECT_USER); prepStmt = dbConnection.prepareStatement(sqlstmt); prepStmt.setString(1, userName); rs = prepStmt.executeQuery(); if (rs.next()) { String storedPassword = rs.getString("ENCRYPTED_USER_PASSWORD"); if ((storedPassword != null) && (storedPassword.trim().equals(password))) { isAuthed = true; } } } catch (SQLException e) { throw new UserStoreException("Authentication Failure. Using sql :" + sqlstmt); } finally { DatabaseUtil.closeAllConnections(dbConnection, rs, prepStmt); } if (log.isDebugEnabled()) { log.debug("User " + userName + " login attempt. Login success :: " + isAuthed); } return isAuthed; }
Override theÂ
getDefaultUserStoreProperties()
 method as seen below to define custom SQL queries and make them configurable.@Override public org.wso2.carbon.user.api.Properties getDefaultUserStoreProperties() { Properties properties = new Properties(); properties.setMandatoryProperties(CustomUserStoreManagerConstants.MANDATORY_PROPERTIES.toArray (new Property[CustomUserStoreManagerConstants.MANDATORY_PROPERTIES.size()])); properties.setOptionalProperties(CustomUserStoreManagerConstants.OPTIONAL_PROPERTIES.toArray (new Property[CustomUserStoreManagerConstants.OPTIONAL_PROPERTIES.size()])); properties.setAdvancedProperties(CustomUserStoreManagerConstants.ADVANCED_PROPERTIES.toArray (new Property[CustomUserStoreManagerConstants.ADVANCED_PROPERTIES.size()])); return properties; }
Set the mandatory, optional and advanced configuration properties as follows.Â
package com.wso2.carbon.custom.user.store.manager; import org.wso2.carbon.user.api.Property; import org.wso2.carbon.user.core.UserStoreConfigConstants; import org.wso2.carbon.user.core.jdbc.JDBCRealmConstants; import java.util.ArrayList; public class CustomUserStoreManagerConstants { public static final ArrayList<Property> MANDATORY_PROPERTIES = new ArrayList<Property>(); public static final ArrayList<Property> OPTIONAL_PROPERTIES = new ArrayList<Property>(); public static final ArrayList<Property> ADVANCED_PROPERTIES = new ArrayList<Property>(); static { setMandatoryProperty(JDBCRealmConstants.DRIVER_NAME, "Driver Name", "", "Full qualified driver name"); setMandatoryProperty(JDBCRealmConstants.URL, "Connection URL", "", "URL of the user store database"); setMandatoryProperty(JDBCRealmConstants.USER_NAME, "User Name", "", "Username for the database"); setMandatoryProperty(JDBCRealmConstants.PASSWORD, "Password", "", "Password for the database"); setProperty(UserStoreConfigConstants.disabled, "Disabled", "false", UserStoreConfigConstants.disabledDescription); setProperty("ReadOnly", "Read Only", "true", "Indicates whether the user store of this realm operates in the user read only mode or not"); setProperty(UserStoreConfigConstants.SCIMEnabled, "SCIM Enabled", "false", UserStoreConfigConstants.SCIMEnabledDescription); setAdvancedProperty("SelectUserSQL", "Select User SQL", "SELECT * FROM TEST_USER WHERE USER_NAME=?", ""); setAdvancedProperty("UserFilterSQL", "User Filter SQL", "SELECT USER_NAME FROM TEST_USER WHERE USER_NAME LIKE" + " ? ORDER BY USER_NAME", ""); setAdvancedProperty("IsUserExistingSQL", "Is User Existing SQL", "SELECT USER_NAME FROM TEST_USER WHERE " + "USER_NAME=? ", ""); } private static void setProperty(String name, String displayName, String value, String description) { Property property = new Property(name, value, displayName + "#" + description, null); OPTIONAL_PROPERTIES.add(property); } private static void setMandatoryProperty(String name, String displayName, String value, String description) { Property property = new Property(name, value, displayName + "#" + description, null); MANDATORY_PROPERTIES.add(property); } private static void setAdvancedProperty(String name, String displayName, String value, String description) { Property property = new Property(name, value, displayName + "#" + description, null); ADVANCED_PROPERTIES.add(property); } }
Register the Custom User Store Manager in the OSGI framework using the following code.
/** * @scr.component name="com.wso2.carbon.custom.user.store.manager.component" immediate="true" * @scr.reference name="realm.service" * interface="org.wso2.carbon.user.core.service.RealmService"cardinality="1..1" * policy="dynamic" bind="setRealmService" unbind="unsetRealmService" */ public class CustomUserStoreManagerServiceComponent { private static Log log = LogFactory.getLog(CustomUserStoreManagerServiceComponent.class); private static RealmService realmService; protected void activate(ComponentContext ctxt) { Hashtable<String, String> props = new Hashtable<String, String>(); CustomUserStoreManager customUserStoreManager = new CustomUserStoreManager(); ctxt.getBundleContext().registerService(UserStoreManager.class.getName(), customUserStoreManager, props); log.info("CustomUserStoreManager bundle activated successfully.."); } protected void deactivate(ComponentContext ctxt) { if (log.isDebugEnabled()) { log.info("CustomUserStoreManager bundle is deactivated"); } } protected void setRealmService(RealmService realmService) { log.debug("Setting the Realm Service"); CustomUserStoreManagerServiceComponent.realmService = realmService; } protected void unsetRealmService(RealmService realmService) { log.debug("UnSetting the Realm Service"); CustomUserStoreManagerServiceComponent.realmService = null; } public static RealmService getRealmService() { return realmService; } }
Deploying and configuring the secondary custom user store manager
Do the following to deploy and configure the secondary custom user store manager in your WSO2 product.
- Compile the custom user store manager code. You will get aÂ
com.wso2.carbon.custom.user.store.manager-1.0.0.jar
 OSGI bundle. - Copy theÂ
com.wso2.carbon.custom.user.store.manager-1.0.0.jar
 file into theÂ<PRODUCT_HOME>/repository/conf/dropins
 folder. Configure theÂ
<PRODUCT_HOME>/repository/conf/datasources/master-datasources.xml
 file as follows to configure the new database.Â<datasource> <name>CustomUserDB</name> <description>Custom User Database</description> <jndiConfig> <name>jdbc/CustomUserDB</name> </jndiConfig> <definition type="RDBMS"> <configuration> <url>jdbc:mysql://localhost:3306/Custom</url> <username>root</username> <password>root</password> <driverClassName>com.mysql.jdbc.Driver</driverClassName> <maxActive>50</maxActive> <maxWait>60000</maxWait> <testOnBorrow>true</testOnBorrow> <validationQuery>SELECT 1</validationQuery> <validationInterval>30000</validationInterval> </configuration> </definition> </datasource>
Start the WSO2 product and log in to the management console using admin/admin credentials.Â
Navigate to Main>Identity>User Stores and click Add. Fill the form with the relevant values as seen below and click Add.
- Navigate to Main>Identity>Users and Roles>List>Roles and click Permissions for the Internal/everyone role. Assign the Login permission to the role.Â
- Logout of the management console and try to login again using the credentials user1/user1.Â