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.
In LDAP, authentication is supplied in the "bind" operation. LDAP v3 supports three types of authentication: anonymous, simple and SASL authentication.
LDAP provides a means to manage user and group membership stored in Active Directory. LDAP is a protocol to authenticate and authorize granular access to IT resources, while Active Directory is a database of user and group information.
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" />
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);
}
}
The LDAP server is disconnecting idle connections that have been idle, that is, no requests transmitted, after a certain period of time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With