| Originally updated on November 26, 2015 by Chuddamani |
Introduction
Many a time customer needs a handle to control login and logout for the application. Older fixpacks had User Exit for Login and now SB2Bi 5.2.6 provides User Exit for Logout as well. A user exit allows one to hook custom java code during certain pre-defined operations.
This document describes a way for customer to use User Exit for Login and Logout. This is just an example and customer can enhance it to use their own implementation.
List of User Exit implementations
Customers can write their own implementation
- to take any action before the authentication of the user happens
- to handle any use case if the user authentication fails
- to handle any use case if the user gets successfully authenticated
- to take any action before the user session is destroyed .i.e user logs out
Interfaces that needs to be implemented
User Exits for Login - Interfaces
- IUserLoginUserExit_postAuthenticateFail -public boolean postAuthenticateFail(Map<String, Object> inargs, Map<String, Object> outargs) throws Exception
This method can be used to execute code after the user authentication is failed.
- IUserLoginUserExit_postAuthenticateSuccess -public boolean postAuthenticateSuccess(Map<String, Object> inargs, Map<String, Object> outargs) throws Exception
This method can be used to execute code after the user authentication is Successful.
- IuserLoginUserExit_preAuthenticate - public boolean preAuthenticate(Map<String, Object> inargs, Map<String, Object> outargs) throws Exception
This method can be used is execute code before the user authentication is done
User Exits for Logout -Interfaces
- IuserLogoutUserExit_beforeSessionDestroyed -
public boolean beforeSessionDestroyed(Map<String, Object> inargs, Map<String, Object> outargs) throws Exception
This method is used to execute code before the user session is destroyed.
Sample User Exit Implementation
Description -
We have a use case where we cover the following -->
1: We prevent multiple logins of the same user (i.e using same credentials). For a user credential only one account will be ACTIVE, any other attempt to login using same credentials will be discarded and recorded for audit purpose.
2: We keep a track of the valid credentials that has been tried or used for login. We also maintain the session history of each valid account – i.e. the duration a valid user was logged in.
3: We keep a track of the invalid credentials that has been tried for login. This helps in identifying an attempt of breach.
Prerequisite Table Structure required for this implementation are-->
1:
Table name-USER_LOGIN_STATUS
Columns
OBJECT_ID -->Unique Primary Key
USERID -->UserId of the user
PASSWORD -->Password of user
STATUS -> PREAUTH (indicates that Authentication has not yet been done)
INVALID (indicates that the authentication failed)
ACTIVE (indicates that the user is currently logged in)
INACTIVE (indicates the user is not logged in)
LOGINTS --> login time
LOGOUTTS --> logout time
Create table command for USER_LOGIN_STATUS
CREATE TABLE temp.USER_LOGIN_STATUS
(
OBJECT_ID int UNSIGNED NOT NULL AUTO_INCREMENT,
USERID varchar(225),
PASSWORD varchar(225),
STATUS varchar(225),
LOGINTS TIMESTAMP,
LOGOUTTS TIMESTAMP,
PRIMARY KEY (OBJECT_ID)
);
2:
Table name-USER_LOGIN_AUDIT
Columns
OBJECT_ID -->Unique Primary Key(Many to one)
USERID -->UserId that has been used for loggin
COMMENT -> Message/Warning/Info
CREATETS -->Specifying the attempt timing.
Create table command for USER_LOGIN_AUDIT
CREATE TABLE temp.USER_LOGIN_AUDIT
(
OBJECT_ID int UNSIGNED NOT NULL AUTO_INCREMENT,
USERID varchar(225),
COMMENT varchar(225),
CREATETS TIMESTAMP,
PRIMARY KEY (OBJECT_ID)
);
Code Implementation
1: Pre Authentication Implemenrtation
File -->IuserLoginUserExit_preAuthenticate_Impl
When user is already logged in, the STATUS is ACTIVE for the user.
If such a user tries to login the method returns false and the authentication for that user is not done and he is redirected to the login page.
The "USER_LOGIN_AUDIT" table is updated with the comments – User is Already Active but another attempt was made to login.
When user logs in for the first time, NO STATUS.
OR
When the user is logged off, the STATUS is INACTIVE for the user and tries to re-login.
If such a user tries to login the "USER_LOGIN_STATUS" table is updated with STATUS as PREAUTH and the method returns true for authentication to take place.
For the invalid credentials the , the STATUS is INVALID
If a user tries with invalid credentials the "USER_LOGIN_STATUS" table is updated with STATUS as PREAUTH , and the method returns true for authentication to take place. The validity of the credentials are not determined in pre-authentication but in post authenticate method even though the first attempt of invalid credentials is known at this step.
|
Code snippet
UserExit_dao dao = new UserExit_dao();
conn = dao.createConnection();
if (conn != null) {
UserLoginDataBean dataBean = (UserLoginDataBean) inargs.get(KEY_DATA_BEAN);
UserLoginStatus userStatus = dao.getUserStatusByUserId(dataBean.getUserID());
if (userStatus != null) {
switch (userStatus.getStatus()) {
case "ACTIVE":
if (userStatus.getPassword().equals(dataBean.getPassword())) {
String comment = "User Already Active but tried login";
dao.insertIntoUserAudit(userStatus.getUserId(),comment,new Timestamp(new Date().getTime()));
return false;
} else {
return true;
}
case "INACTIVE":
dao.insertUserstatus(dataBean);
return true;
case "INVALID":
dao.insertUserstatus(dataBean);
return true;
}
} else {
dao.insertUserstatus(dataBean);
}
}
|
2:Post Authentication implementation (when authentication is successful)
File--> IuserLoginUserExit_postAuthenticateSuccess_Impl
This code is executed when the authentication is successful, the "USER_LOGIN_STATUS" table is updated with STATUS as ACTIVE and the "USER_LOGIN_AUDIT" table is updated with the login time and comments.
|
Code snippet
UserExit_dao dao = new UserExit_dao();
conn = dao.createConnection();
if (conn != null) {
UserLoginDataBean dataBean = (UserLoginDataBean) inargs.get("dataBean");
UserLoginStatus userStatus = dao.getUserStatusByUserId(dataBean.getUserID());
if (userStatus != null) {
if (userStatus.getStatus().equals("PREAUTH")) {
userStatus.setStatus("ACTIVE");
userStatus.setPassword(dataBean.getPassword());
dao.updateUserstatusData(userStatus);
String comment = "Authentication successful";
dao.insertIntoUserAudit(userStatus.getUserId(),
comment, userStatus.getLoginTs());
}
}
conn.close();
}
|
3:Post Authentication implementation (when authentication fails)
File--> IuserLoginUserExit_postAuthenticateFail_Impl
This code is executed when the authentication is failed , the "USER_LOGIN_STATUS" table is updated with STATUS as INVALID.
|
Code snippet
UserExit_dao dao = new UserExit_dao();
conn = dao.createConnection();
if (conn != null) {
UserLoginDataBean dataBean = (UserLoginDataBean) inargs.get(KEY_DATA_BEAN);
UserLoginStatus userStatus = dao.getUserStatusByUserId(dataBean.getUserID());
if (userStatus != null) {
/*
* the below if is called because we are allowing an active
* user with different password to go for authentication
*/
if (userStatus.getStatus().equalsIgnoreCase("ACTIVE")) {
return true;
} else {
userStatus.setStatus("INVALID");
userStatus.setLogoutTs(new Timestamp(new Date().getTime()));
dao.updateUserstatusData(userStatus);
}
}
}
|
4: Implementation before session is destroyed(on logout or session timeout)
File ---> IuserLogoutUserExit_beforeSessionDestroyed_Impl
This code is executed when the user logs out or if the session times out. Here the "USER_LOGIN_STATUS" table is updated with STATUS as INACTIVE , since the user is no more logged in.
|
Code snippet
try {
UserExit_dao dao = new UserExit_dao();
conn = dao.createConnection();
if (conn != null) {
String userid=((UserLogoutDataBean)(inargs.get(KEY_DATA_BEAN))).getUserID();
UserLoginStatus userStatus = dao.getUserStatusByUserId(userid);
userStatus.setLogoutTs(new Timestamp(new Date().getTime()));
userStatus.setStatus("INACTIVE");
dao.updateUserstatusData(userStatus);
}
|
Jar Installation Procedure
Step 1:
Install the implementation jar provided ("implementation.jar") using install3rdParty.sh script. This adds JAR entry to properties/dynamiclcasspath.cfg and properties/dynamiclcasspathAC.cfg (for containers)
command--> /install3rdParty.sh userexit 1_0 -j ../../UserExitImpl.jar
Step 2:
Modify following xmls as below:
For userExit on login – install/properties/userexit/UserLoginUserExits.xml
For userExit on logout - install/properties/userexit/UserLogoutUserExits.xml
Example ->
Entries to be done in UserLoginUserExits.xml
|
<bean id="com.sterlingcommerce.woodstock.userexit.services.userlogin.interfaces.IUserLoginUserExit_preAuthenticate_Test"
class="com.sterlingcommerce.woodstock.userexit.services.userlogin.UserLoginUserExit">
<property name="implementations">
<list>
<value>com.userexit.test.IUserLoginUserExit_preAuthenticate_Impl</value>
</list>
</property>
<property name="generalParameters">
<props>
<prop key="return.on.exception">false</prop>
<prop key="pool.size">5</prop>
<prop key="maximum.queue.length">5</prop>
<prop key="wait.time">10</prop>
<prop key="execution.threshold.time">600000</prop>
</props>
</property>
</bean>
...
...
<bean id="com.sterlingcommerce.woodstock.userexit.services.userlogin.interfaces.IUserLoginUserExit_postAuthenticateFail_Test"
class="com.sterlingcommerce.woodstock.userexit.services.userlogin.UserLoginUserExit">
<property name="implementations">
<list>
<value>com.userexit.test.IUserLoginUserExit_postAuthenticateFail_Impl</value>
</list>
</property>
<property name="generalParameters">
<props>
<prop key="return.on.exception">false</prop>
<prop key="pool.size">5</prop>
<prop key="maximum.queue.length">5</prop>
<prop key="wait.time">10</prop>
<prop key="execution.threshold.time">600000</prop>
</props>
</property>
</bean>
..
..
<bean id="com.sterlingcommerce.woodstock.userexit.services.userlogin.interfaces.IUserLoginUserExit_postAuthenticateSuccess_Test"
class="com.sterlingcommerce.woodstock.userexit.services.userlogin.UserLoginUserExit">
<property name="implementations">
<list>
<value>com.userexit.test.IUserLoginUserExit_postAuthenticateSuccess_Impl</value>
</list>
</property>
<property name="generalParameters">
<props>
<prop key="return.on.exception">false</prop>
<prop key="pool.size">5</prop>
<prop key="maximum.queue.length">5</prop>
<prop key="wait.time">10</prop>
<prop key="execution.threshold.time">600000</prop>
</props>
</property>
</bean>
|
Entries to be done in UserLogoutUserExits.xml
|
<bean id="com.sterlingcommerce.woodstock.userexit.services.userlogout.interfaces.IUserLogoutUserExit_beforeSessionDestroyed"
class="com.sterlingcommerce.woodstock.userexit.services.userlogout.UserLogoutUserExit">
<property name="implementations">
<list>
<value>com.userexit.test.IUserLogoutUserExit_beforeSessionDestroyed_Impl</value>
</list>
</property>
<property name="generalParameters">
<props>
<prop key="return.on.exception">false</prop>
<prop key="pool.size">10</prop>
<prop key="maximum.queue.length">10000</prop>
<prop key="wait.time">10</prop>
<prop key="execution.threshold.time">600000</prop>
</props>
</property>
</bean>
|
Step 3
Stop and Start SI to take the effect.
Note - Changes to this xml won’t survive with upgrade/fixpack installation. They need to be re-configured.
Testing monitoring
This can be tested as below-->
Login as a particular user, say with credentials admin /password. Now if the same credentials are used to login again while the first user is already logged in , we get the following error
Table details :--
Tablename-->USER_LOGIN_STATUS Indicates the session details
Description->The Below table indicates the different STATUS of the users and their session details is also captured. The INVALID user credentials are also captured.
Tablename-->USER_LOGIN_AUDIT indicates the login attempt details
Description-->The below table indicates that if an already ACTIVE user tries to login , then it is captured. as highlighted in the snapshot..
#DataExchange#IBMSterlingB2BIntegratorandIBMSterlingFileGatewayDevelopers