Tomcat 7.0.14 LDAP authentication

16,824

Solution 1

I was able to figure out the reason for the warning and also a way to get rid of it.

The reason for the warning was that the LDAP server is closing all the connections that have been idle for more than 5 minutes. The LDAP server admin told me that it's recommended to close the connection immediately after each login request, because the number of available handles is limited. Tomcat's JNDIRealm, however, doesn't offer a way to configure this, so I resolved the problem by extending the JNDIRealm class and overriding the authenticate(..) method. All that needs to be done is to close the connection to the LDAP server after each authentication request and the warnings are gone.

Note that the package needs to be the same as JNDIRealm class, because otherwise it's not possible to access the context variable.

package org.apache.catalina.realm;

import java.security.Principal;

public class CustomJNDIRealm extends JNDIRealm {
  @Override
  public Principal authenticate(String username, String credentials) {
  Principal principal = super.authenticate(username, credentials);

    if (context != null) {
      close(context);
    }
    return principal;
  }
}

Generated jar needs to be put under Tomcat's lib folder and change the className in the application's context.xml to org.apache.catalina.realm.CustomJNDIRealm. Then just restart Tomcat and that's it.

<Realm className="org.apache.catalina.realm.CustomJNDIRealm"  
  connectionURL="ldaps://ldap-company.com"  
  userPattern="uid={0},dc=company,dc=com"  
  roleBase="ou=groups,o=company"  
  roleName="uid"  
  roleSearch="uniqueMember={0}"  
  roleSubtree="true" /> 

Solution 2

I am answering, because this is a current research topic for me, as we currently extend the JNDIRealm for our needs.

The realm will retry after the warning, so the suggested patch is just beautifying the logfile. Later versions of tomcat (7.0.45 iirc) will beautify the logmessage to make clear, that there is a retry attempt done.

If you want to have the realm doing authentication with a fresh connection every time, it should be sufficient to use this class (I have not tested this implementation but will if our realm is done):

package org.apache.catalina.realm;

import java.security.Principal;

public class CustomJNDIRealm extends JNDIRealm {
  @Override
  public Principal authenticate(String username, String credentials) {
    Principal principal = null;
    DirContext context = null;
    try {
       context = open();
       principal = super.authenticate(context, username, credentials);
    }
    catch(Throwable t) {
       // handle errors
       principal = null;
    }
    finally {
       close(context); // JNDIRealm close() takes care of null context
    }

    return principal;
  }

  @Override
  protected DirContext open() throws NamingException {

      // do no longer use the instance variable for context caching
      DirContext context = null;

      try {

          // Ensure that we have a directory context available
          context = new InitialDirContext(getDirectoryContextEnvironment());

      } catch (Exception e) {

          connectionAttempt = 1;

          // log the first exception.
          containerLog.warn(sm.getString("jndiRealm.exception"), e);

          // Try connecting to the alternate url.
          context = new InitialDirContext(getDirectoryContextEnvironment());

      } finally {

          // reset it in case the connection times out.
          // the primary may come back.
          connectionAttempt = 0;

      }

      return (context);

  }


}

Solution 3

The LDAP server is disconnecting idle connections that have been idle, that is, no requests transmitted, after a certain period of time.

Share:
16,824
Dinky Jackson
Author by

Dinky Jackson

Updated on June 22, 2022

Comments

  • Dinky Jackson
    Dinky Jackson almost 2 years

    I have a web application running on Tomcat 7.0.14 and I'm using LDAP for user authentication. The problem is that when a user logs in after an inactive period the following warning comes out. The inactive period doesn't have to be long, as only few minutes is enough. However, the user is able to log in despite of the warning. From the users' point of view the application behaves normally, but Tomcat log reveals the warning below.

    Jun 6, 2012 9:41:19 AM org.apache.catalina.realm.JNDIRealm authenticate  
    WARNING: Exception performing authentication  
    javax.naming.CommunicationException [Root exception is java.io.IOException: connection closed]; remaining name ''  
            at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:157)  
            at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2685)  
            at com.sun.jndi.ldap.LdapCtx.ensureOpen(LdapCtx.java:2593)  
            at com.sun.jndi.ldap.LdapCtx.ensureOpen(LdapCtx.java:2567)  
            at com.sun.jndi.ldap.LdapCtx.doSearch(LdapCtx.java:1932)  
            at com.sun.jndi.ldap.LdapCtx.doSearchOnce(LdapCtx.java:1924)  
            at com.sun.jndi.ldap.LdapCtx.c_getAttributes(LdapCtx.java:1317)  
            at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_getAttributes(ComponentDirContext.java:231)  
            at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:139)  
            at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:127)  
            at javax.naming.directory.InitialDirContext.getAttributes(InitialDirContext.java:140)  
            at org.apache.catalina.realm.JNDIRealm.bindAsUser(JNDIRealm.java:1621)  
            at org.apache.catalina.realm.JNDIRealm.checkCredentials(JNDIRealm.java:1480)  
            at org.apache.catalina.realm.JNDIRealm.authenticate(JNDIRealm.java:1131)  
            at org.apache.catalina.realm.JNDIRealm.authenticate(JNDIRealm.java:1016)  
            at org.apache.catalina.authenticator.FormAuthenticator.authenticate(FormAuthenticator.java:282)  
            at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:440)  
            at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)  
            at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)  
            at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:563)  
            at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)  
            at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:399)  
            at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:317)  
            at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:204)  
            at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:311)  
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)  
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)  
            at java.lang.Thread.run(Thread.java:636)  
    Caused by: java.io.IOException: connection closed  
            at com.sun.jndi.ldap.LdapClient.ensureOpen(LdapClient.java:1576)  
            at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:155)  
            ... 27 more  
    

    The LDAP configuration is in the application's context.xml file:

    <Realm className="org.apache.catalina.realm.JNDIRealm"  
        connectionURL="ldaps://ldap-company.com"  
        userPattern="uid={0},dc=company,dc=com"  
        roleBase="ou=groups,o=company"  
        roleName="uid"  
        roleSearch="uniqueMember={0}"  
        roleSubtree="true" />  
    

    I've found posts about this problem from several forums, but no one seems to have figured out the solution.