Standalone Tomcat with jBoss (2nd Edition)

This tutorial desribes, how to install and configure a standalone Tomcat, so that a deployed webapp can connect to a jBoss and use the authentication of the application server. This method is decoupled from the login module or authentication type (LDAP, Database, …), respectively. It differs from the approach described in Standalone Tomcat with jBoss plus authentication against LDAP, in that it allows for parallel logged in users and it does not need to authenticate to LDAP/Database on both sides, but on the jBoss only.

The tutorial has been successfully tested with the following versions of third party projects:

  • Tomcat 7.0.12
  • jBoss 5.1.0.GA

The following steps are sufficient advices for the impatient reader, in order to run the tomcat standalone with a connection to jboss. A more detailed description about what is going on here can be found in using JAAS login modules from jboss in tomcat. Here are the instructions:

  • download tomcat:
http://apache.mirror.clusters.cc/tomcat/tomcat-7/v7.0.12/bin/apache-tomcat-7.0.12.zip
  • decompress the file
  • edit $CATALINA_HOME/bin/catalina.sh:
export JAVA_OPTS="$JAVA_OPTS -Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config
    -Djava.naming.provider.url=localhost:1399"
  • We’re using JAAS for authentication. We declare two login modules. The ClientLoginModule builds the security context for EJB calls, so that the application server gets the credentials for authentication. The MyLoginModule authenticates the user via jBoss and builds the subject for Tomcat. In order to configure JAAS create/edit the file $CATALINA_HOME/conf/jaas.config:
someapp {
 // Create security context for jboss
 org.jboss.security.ClientLoginModule required;
     multi-threaded="true";
 // Login module for tomcat
 org.someorg.security.MyLoginModule required;
};
  • edit $CATALINA_HOME/conf/server.xml:
    • comment out all existing <Realm>-tags
    • add the following block:
<Realm className="org.apache.catalina.realm.JAASRealm"
       appName="someapp"
       useContextClassLoader="false"
       userClassNames="org.jboss.security.SimplePrincipal"
       roleClassNames="org.jboss.security.SimpleGroup"
/>
  • Above we used a class called org.someorg.security.MyLoginModule. This class must be on the class path of tomcat and jboss ($CATALINA_HOME/lib and $JBOSS_SERVER_CONFIG_HOME/lib). Here is an implementation of the class:
package org.someorg.security;
public class MyLoginModule
        extends org.jboss.security.auth.spi.UsernamePasswordLoginModule {
 private String credential;
 private String username;
 private final static Map<String, String> secPWDs =
        Collections.synchronizedMap(new HashMap<String, String>());
 // This is a session bean providing some methods for logins
 private UserController uC;

 public final static InitialContext loginUser(final String username) {
    	final String credential = secPWDs.get(username);
    	if(credential != null) {
    		Properties env = new Properties();
    		env.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.security.jndi.JndiLoginInitialContextFactory");
    		env.setProperty(Context.SECURITY_PRINCIPAL, username);
    		env.setProperty(Context.SECURITY_CREDENTIALS, credential);
    		env.setProperty("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
    		env.setProperty("java.naming.provider.url", System.getProperty("java.naming.provider.url"));
    		try {
    			return new InitialContext(env);
    		} catch (NamingException e) {
    			throw new IllegalStateException(e);
    		}
    	}
    	return null;
    }

 @Override
 protected Group[] getRoleSets() throws LoginException {
	List<Group> groups = new LinkedList<Group>();
	if(this.uC != null) {
		// retrieve the user roles.
		for(String roleName : uC.getUserRoles()) {
			groups.add(new SimpleGroup(roleName));
		}
	}
	return groups.toArray(new Group[groups.size()]);
 }

	@Override
	@SuppressWarnings("unchecked")
	 public boolean login() throws LoginException {
        String[] info = getUsernameAndPassword();
        username = info[0];
        credential = info[1];


        if (super.login()) {
        	boolean success = false;
            try {
            	secPWDs.put(username, credential);
                final InitialContext ctx = loginUser(username);
                try {
                	this.uC = (UserControllerRemote) ctx.lookup("someapp/UserController/remote");
                	User user = uC.getUserOrNull();
                	if(user != null && user.getLoginName().toLowerCase().trim().equals(username.toLowerCase().trim())) {
                		this.sharedState.put("javax.security.auth.login.name", username);
		                this.sharedState.put("javax.security.auth.login.password", password);
	                	return (success = true);
                	}
                	secPWDs.remove(username);
                	return (success = false);
                }
                finally {
                	ctx.close();
                }
            }
            catch (Exception e) {
                throw new LoginException(e.getMessage());
            }
            finally {
            	super.loginOk = success;
            }
        }
        else {
            return false;
        }
 }

 @Override
 public boolean logout() throws LoginException {
	secPWDs.remove(username);
	uC = null;
	return super.logout();
 }
}
  • We can load a session bean in a servlet like this (the login has to be done at least once per request, because a new request might run in another thread, so a call to a session bean would be done with the user anonymous):
String user = httpRequest.getRemoteUser();
InitialContext ctx = MyLoginModule.loginUser(user);
MySessionBean sb = (MySessionBean)ctx.lookup("myapp/MySessionBean/remote");
...
  • The UserController may look like this:

@Stateless
@Remote(UserControllerRemote.class)
@Local(UserControllerLocal.class)
@RemoteBinding(jndiBinding="someapp/UserController/remote")
@LocalBinding(jndiBinding="someapp/UserController/local")
@RolesAllowed({"someappuser", "someappadmin"})
public class UserController {
 @PersistenceContext(unitName = "SOMEAPP_DB")
 private EntityManager manager;

 public enum UserRole {
 	someappuser,
 	someappadmin;
 }

 public Collection<String> getUserRoles() {
	Collection<String> groups = new LinkedList<String>();
	for(UserRole role: UserRole.values()) {
		if(this.sessionContext.isCallerInRole(role.name())) {
			groups.add(role.name());
		}
	}
	return groups;
 }

 @PermitAll
 public User getUserOrNull() {
	final String userName = sessionContext.getCallerPrincipal().getName();
	return this.manager.find(User.class, userName);
 }
}
  • In order to get everything running jbosssx.jar and jbossall-client.jar (part of the jboss package) has to be on the classpath. So get it and put it into the $CATALINA_HOME/lib folder.

Leave a Reply

Your email address will not be published. Required fields are marked *