Setup Tomcat JDBCRealm

This example, shows us how to setup Tomcat JDBCRealms to use it within your projects.This type of realm involves registering usernames, passwords and assigned roles inside a database which is then used by authentication methods.


The following steps explain how.implement this approach:


This example has been built using:


Tomcat Web Server 7.0.20. You can download the latest version here.


MySQL Database - 5.0.45-community-nt. You can download the latest version here.


1.- Set up JDBCRealms on Tomcat,  locate the server.xml configuration file in <TOMCAT_HOME>/conf/,  then, add the following lines:


<Realm className="org.apache.catalina.realm.JDBCRealm"
  driverName="com.mysql.jdbc.Driver"
  connectionURL="jdbc:mysql://localhost/smartgwt?user=smart&amp;password=smart"
  userTable="isg_users" userNameCol="user_name" userCredCol="user_passwd"
  userRoleTable="isg_users_roles" roleNameCol="role_name"/>


Note. Please copy the MySql  jdbc library to <TOMCAT_HOME>/lib/.


2.- Create a new database with the following tables:


create table `isg_roles` (
	`ROLE_NAME` varchar (60)
);
insert into `isg_roles` (`ROLE_NAME`) values('admin');
insert into `isg_roles` (`ROLE_NAME`) values('manager');
insert into `isg_roles` (`ROLE_NAME`) values('manager-gui');
insert into `isg_roles` (`ROLE_NAME`) values('manager-script');
insert into `isg_roles` (`ROLE_NAME`) values('manager-status');
insert into `isg_roles` (`ROLE_NAME`) values('tomcat');

create table `isg_users` (
	`USER_NAME` varchar (60),
	`USER_PASSWD` varchar (60)
);
insert into `isg_users` (`USER_NAME`, `USER_PASSWD`) values('tomcat','tomcat');
insert into `isg_users` (`USER_NAME`, `USER_PASSWD`) values('admin','admin');

create table `isg_users_roles` (
	`USER_NAME` varchar (60),
	`ROLE_NAME` varchar (60)
);
insert into `isg_users_roles` (`USER_NAME`, `ROLE_NAME`) values('admin','admin');
insert into `isg_users_roles` (`USER_NAME`, `ROLE_NAME`) values('admin','manager');
insert into `isg_users_roles` (`USER_NAME`, `ROLE_NAME`) values('admin','manager-gui');
insert into `isg_users_roles` (`USER_NAME`, `ROLE_NAME`) values('admin','manager-status');
insert into `isg_users_roles` (`USER_NAME`, `ROLE_NAME`) values('tomcat','tomcat');


3.- Create a user with all access rights on the created tables of the new database. In this example, the user is “smart” and the database name is “smartgwt”


4.- Set up the web.xml file for the example to use the authentication method as Form based:


    <login-config>
		<auth-method>FORM</auth-method>
		<realm-name>User Auth</realm-name>
			<form-login-config>
				<form-login-page>/login.html</form-login-page>
				<form-error-page>/error.html</form-error-page>
			</form-login-config>
    </login-config>
    <security-role>
		<role-name>*</role-name>
    </security-role>

   <security-constraint>
	  <web-resource-collection>
		  <web-resource-name>Sample Application</web-resource-name>
		  <url-pattern>*.html</url-pattern>
		  <url-pattern>/securitytomcat/sc/IDACall/*</url-pattern>
		  <http-method>POST</http-method>
		  <http-method>GET</http-method>
	  </web-resource-collection>

	  <auth-constraint>
		<role-name>*</role-name>
	  </auth-constraint>
    </security-constraint>


Here there is an example of a replacement login page with some attractive styling.


Currently, all of the above steps have related to setting up JBDCRealms on Tomcat.


The following steps explain how to implement additional functionality:


5.- Create an interface for editing user and role assignments. This will only be accessible to a user with role "admin". For any other roles, the system will throw an error.


Create 3 new *.ds.xml files:


users.ds.xml
<DataSource
    ID="users"
    serverConstructor="com.smartgwt.sample.server.UsersDataSource"
>
    <fields>
        <field name="userName"        type="text"  title="User Name"      primaryKey="true" />
    </fields>
</DataSource>


roles.ds.xml
<DataSource
    ID="roles"
    serverConstructor="com.smartgwt.sample.server.RolesDataSource"
>
    <fields>
        <field name="roleName"  	type="text"      title="Role Name" primaryKey="true"/>
    </fields>

</DataSource>


usersRoles.ds.xml
<DataSource
    ID="usersRoles"
    serverConstructor="com.smartgwt.sample.server.UsersRolesDataSource"
>
    <fields>
        <field name="userName"  	type="text"      title="User Name" primaryKey="true"/>
        <field name="roleName"  	type="text"      title="Role Name" primaryKey="true"/>
    </fields>

    <operationBindings>
        <operationBinding operationType="add"
        requiresRole="admin" requiresAuthentication="true"/>

        <operationBinding operationType="remove"
        requiresRole="admin" requiresAuthentication="true"/>
    </operationBindings>
</DataSource>


After the user is authenticated, we save the assigned roles for this user. Create a new servlet to do this (in this case we will call it ServletLogin).


The following code describes this new servlet:


ServletLogin.java
Principal principal = request.getUserPrincipal();
GenericPrincipal genericPrincipal = (GenericPrincipal) principal;
final String[] roles = genericPrincipal.getRoles();
String userRoles = "";
for (int i = 0; i < roles.length; i++) {
    userRoles = userRoles + roles[i]+",";
}
PrintWriter writer = response.getWriter();
writer.append(userRoles);


In web.xml, add the following lines:


    <servlet>
	<servlet-name>ServletLogin</servlet-name>
	<servlet-class>com.smartgwt.sample.server.ServletLogin</servlet-class>
    </servlet>
    <servlet-mapping>
            <servlet-name>ServletLogin</servlet-name>
	 <url-pattern>/ServletLogin</url-pattern>
    </servlet-mapping>


In client-side function, add this code to invoke the new servlet using the RPCManager:


RPCRequest request = new RPCRequest();
request.setActionURL("ServletLogin");
RPCManager.sendRequest(request,new RPCCallback(){
public void execute(RPCResponse response, Object rawData, RPCRequest request) {
	        	 User.setRoles(rawData.toString());
       			 if (User.hasRole("admin")) {
	        		theTabs.addTab(tabRoles);
	        	 	theTabs.addTab(tabItems);
	        	 } else {
	        		 theTabs.addTab(tabItems);
	        	 }
	         }
	     });


In the above snippet, if the authenticated user has the role admin, then the user will have access to the interface for editing user and role assignments.


Additionally, we need another class on the client-side.  This is the User Class for the actual user roles:


        private static String roles;
	public static void setRoles(String ro) {
		roles = ro;
	}

	public static boolean hasRole(String role) {
		if (roles.contains(role)) {
			return true;
		}
		return false;
	}


Finally, below are some screenshots of this example in action.


Picture 1.- Interface of editing for the user with admin role.



Picture 2.- Error validation example (where the user has access to the editing interface via another role(e.g. manager), but does not have the admin role required for editing).


6.- Display the effects of the configuration: editRequiresRole="admin" and viewRequiresRole="admin" in the .ds.xml file.


For this example,  use the supplyItem.ds.xml file:


supplyItem.ds.xml
<DataSource
    ID="supplyItem"
    recordXPath="/List/supplyItem"
    dataURL="ds/test_data/supplyItem.data.xml"
>
    <fields>
        <field name="itemID"      type="sequence" hidden="true"       primaryKey="true"/>
        <field name="itemName"    type="text"     title="Item"        length="128"       required="true"
        	   editRequiresRole="admin" />
        <field name="SKU"         type="text"     title="SKU"         length="10"        required="true"
               editRequiresRole="admin" />
        <field name="description" type="text"     title="Description" length="2000"
               viewRequiresRole="admin" />
        <field name="category"    type="text"     title="Category"    length="128"       required="true"
               editRequiresRole="admin"
               foreignKey="supplyCategory.categoryName"/>
        <field name="units"       type="enum"     title="Units"       length="5" editRequiresRole="admin">
            <valueMap>
                <value>Roll</value>
                <value>Ea</value>
                <value>Pkt</value>
                <value>Set</value>
                <value>Tube</value>
                <value>Pad</value>
                <value>Ream</value>
                <value>Tin</value>
                <value>Bag</value>
                <value>Ctn</value>
                <value>Box</value>
            </valueMap>
        </field>
        <field name="unitCost"    type="float"    title="Unit Cost"   required="true" editRequiresRole="Admin">
            <validators>
                <validator type="floatRange" min="0" errorMessage="Please enter a valid (positive) cost"/>
                <validator type="floatPrecision" precision="2" errorMessage="The maximum allowed precision is 2"/>
            </validators>
        </field>
        <field name="inStock"   type="boolean"  title="In Stock" viewRequiresRole="admin" />
        <field name="nextShipment"  type="date" title="Next Shipment" viewRequiresRole="admin" />
    </fields>
</DataSource>


The following screenshots show editRequiresRole and viewRequiresRole in action.


Picture 3. For a  user with admin role access.



Picture 4. For a user without the admin role access, In this example, the fields with editRequiresRole="admin" will be read-only, and the fields with viewRequiresRole="admin", will not be visible.


Picture 5. Error validation example where the user does not have Admin role access and wants to save data.


7.- Finally, below is an example  of how the re-login mechanism works. This occurs when the session of the user is invalidated, si if the user wants to continue editing, they will be forced to re-authenticate.


Picture 6. The window re-login.



Picture 7. Username/password are incorrect validation check.



The following code is required inside the login page, to use the re-login mechanism.


<SCRIPT>//'"]]>>isc_loginRequired
//
// Embed this whole script block VERBATIM into your login page to enable
// SmartClient RPC relogin.

while (!window.isc && document.domain.indexOf(".") != -1) {
    try {

        if (parent.isc == null) {
            document.domain = document.domain.replace(/.*?\./, '');
            continue;
        }
        break;
    } catch (e) {
        document.domain = document.domain.replace(/.*?\./, '');
    }
}

var isc = top.isc ? top.isc : window.opener ? window.opener.isc : null;
if (isc) isc.RPCManager.delayCall("handleLoginRequired", [window]);
</SCRIPT>


While the user is being re-authenticated, the requested operation is put on hold. If the re-authentication is successful, the operation on hold, is released and continues as normal.

Note: We have seen some installations of JDBC Realms where the following request is required, or the system would not accept credentials submitted by the relogin form:

RPCRequest request = new RPCRequest();
request.setActionURL("loginSuccessMarker.html");
RPCManager.sendRequest(request);

You can find more information relating to the re-login mechanism here.


You can download the source code of this example here.