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:
- 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. TheMyLoginModule
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:
- comment out all existing
<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.