Tuesday, March 8, 2016

How to Write a Custom User Store Manager - WSO2 Identity Server 5.1.0

WSO2 Identity Sever OOTB support following user stores.
  • org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager
  • org.wso2.carbon.user.core.ldap.ReadOnlyLDAPUserStoreManager
  • org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager
  • org.wso2.carbon.user.core.ldap.ActiveDirectoryLDAPUserStoreManager
  • org.wso2.carbon.identity.user.store.remote.CarbonRemoteUserStoreManger
There are some cases, we have to write a custom implementation. Here I am explaining an step by step guide of how to write a custom users store manager for Identity Server 5.1.0. 

Requirement: A company already has a user database and need to authenticate to Identity Server through that database.

Following is the schema of 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);


Writing Custom User Store Manager
  • You can find the sample code from here
  • Our custom User store is a jdbc based user store and we can write it by extending the org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager.
  • We need to override the doAuthenticate method to authenticate using the new database. 


 @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;
    }
  • We have to define custom SQL queries and we can make them as configurable by overriding getDefaultUserStoreProperties method as follows.

  @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;
    }


  • We can set the mandatory, optional and advanced configuration 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 Custom User Store Manager in OSGI framework

/**
 * @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;
    }
}


Identity Server Configurations. 


  • Compile the custom user store manager code and then you will get com.wso2.carbon.custom.user.store.manager-1.0.0.jar OSGI bundlle.
  • Copy com.wso2.carbon.custom.user.store.manager-1.0.0.jar into <IS_HOME>/repository/components/dropins folder.
  • Configure new database in <IS_HOME>//repository/conf/datasources/master-datasources.xml file as follows

<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 Identity Server.
  • Go to Add User button inside Home/Identity.




  • Then you can fill the configurations as above image
  • Then assign login permission to Internal/everyone role 
  • Try to login to management console using user1/user1



4 comments:

  1. Hi, your post is very interesting, i didn't find anything else about that. My need is a little different from this, i have an external LDAP Read Only and i have to extends the attributes user.
    My question is: I have to create an ReadOnlyLDAPUserStoreManager implementation for manage new attributes ? And how configure user-mgmt.xml? I have to add an other User Store (for example a MySql DB) for save the new attributes ? If you have any idea, please reply! :)

    ReplyDelete
  2. my custom user store manager class is not showing in drop down.

    ReplyDelete
  3. Best casino games for iPhone and Android
    The best way to win real money at a casino is by playing the most 1 1 토토 popular casino 여수 휴게텔 games. Players 마추 자 사이트 can enjoy the best bonuses and promotions 블랙 잭 룰 in every slot game Free Spins: No Deposit BonusMobile Bonus: 마라톤벳 Deposit BonusMobile Bonus: Deposit Bonus

    ReplyDelete