Programmatic authentication in Tomcat

I had to programmatically authenticate a principal using the security realm defined in Tomcat. I needed to do so because I didn’t want to reinvent the wheel. But in my case the J2EE container authorizations defined in the web.xml DD didn’t fit well. I also had to programmatically verify that user belonged to a role and the realm API let you do that. The resources to which users were restricted are not URLs but actions and the authorizations are defined in a specific configuration file and not in the web.xml file. I didn’t want to have a specific URI for each action.

With Weblogic server doing programmatic authentication it’s pretty easy. Just use the following code

int retcode = ServletAuthentication.weak(username,password,session);

for WLS 8.1 and

int retcode =  ServletAuthentication.weak(username,password,request,response);
for WLS 9 For Tomcat, i managed to "mimic" containter authentication but haven't fully been able to do it. It's possible through JMX to access the configured realm. For instance, use the following code to retrieve the realm through a local connection to the MBean server (using JSR 160). I left commented code to access the MBean server remotely.
package com.xxx.integ2.tomcat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;
import org.apache.catalina.Manager;
import org.apache.catalina.Realm;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JMXHelper {
public static final String SERVER_URL="service:jmx:rmi:///jndi/rmi://localhost:9004/jmxrmi";
private transient final static Log LOG = LogFactory.getLog(JMXHelper.class);
public static Realm getRealm() {
try {
JMXServiceURL url = new JMXServiceURL(SERVER_URL);
Map environment = new HashMap();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
environment.put(Context.PROVIDER_URL, "rmi://"+url.getHost()+":"+url.getPort());
//MBeanServerConnection connection = JMXConnectorFactory.connect(url,environment).getMBeanServerConnection() ;
// Retrieve MBeanserver
List mbeanServers = MBeanServerFactory.findMBeanServer(null);
MBeanServer mBeanServer = null;
if (mbeanServers != null && mbeanServers.size() > 0) {
mBeanServer = (MBeanServer) mbeanServers.get(0);
}
String objName = "Catalina:j2eeType=WebModule,name=//localhost/manager,J2EEApplication=none,J2EEServer=none";
ObjectName contextObjectName = new ObjectName(objName);
Object contextManager = mBeanServer.getAttribute(contextObjectName, "manager");
//Object contextManagedResource = connection.getAttribute(contextObjectName, "managedResource");
Manager manager = (Manager)contextManager;
Realm realm = manager.getContainer().getRealm(); return realm;
} catch (Exception e) {
LOG.error("A problem occured retrieving the security realm",e);
}
return null;
}
}
I also had to add the following jar to the common.loader in catalina.properties file
${catalina.home}/server/lib/*.jar
to be able to load the catalina API in my webapp. For remote access using the JVM's RMI connector of the JVM's internal MBean server, add the following options when starting the JVM:
-Dcom.sun.management.jmxremote.port=9004 (-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.pass.file=${tomcat.home}/jmxremote.password -Dcom.sun.management.jmxremote.ssl=false

The only problem is that you only delegate authentication to the JNDI realm but you are not really authenticating the user to the container. It means that method getUserPrincipal from HTTPServletRequest (request.getUserPrincipal()) will return null. One way to check that a user is authenticated would be to store the principal in the user’s session. Then write a servlet filter or Spring MVC ‘s interceptor checking that a principal is stored in the user’s session and redirecting to a login page in case it’s not.

Interesting resources:

Tomcat Active Directory realm

Even if configuring a JNDI realm for Tomcat is pretty well documented connecting Tomcat to Active Directory is not really.

After my post about Weblogic and active directory authentication and authorizations.

Here’s a just an example of a configuration of the JNDI realm for ADS in order to use the container ‘s authentication and authorization features.It might help some…

<realm classname="org.apache.catalina.realm.JNDIRealm"
debug="99" connectionurl="ldap://directory:389"
connectionname="CN=manager,CN=Users,DC=mydomain,DC=net"
connectionpassword="helloworld"
userbase="OU=US_USERS,O=US,DC=mydomain,DC=net"
usersearch="(&amp;(sAMAccountName={0})(objectclass=user))"
rolebase="OU=US_GROUPS,OU=US,DC=mydomain,DC=net"
usersubtree="true"
rolename="cn"
rolesubtree="true"
rolesearch="(&amp;(member={0})(objectclass=group))" />

Notes:

  • By default with ADS, it seems that anonymous read of LDAP entries is forbidden. So you need to provide a “connectionName” with its password to enable the retrieval of users and roles.
  • Since the configuration of the JNDI realm is done in the server.xml file the & character used in LDAP filters must be escaped by using its entity
  • Roles are directly mapped to users with the role search filter where {0} is the Distinguished Name of eachuser (retrieved with the usersearch filter). It doesn’t seem that like in Weblogic you can map roles to principalsin a JEE application server’s specific Deployment Descriptor.

In the next post, I’ll show how to access the realm from your webapp in order to use your own home made authentication and authorization frameworkwhile leveraging at the same time the Tomcat JNDI’s realm.