我有一个在Tomcat 7.0.14上运行的Web应用程序,我使用LDAP进行用户身份验证。问题是当用户在非活动期后登录时会出现以下警告。非活动期不必很长,只需几分钟即可。但是,尽管有警告,用户仍可以登录。来自用户'应用程序行为正常,但Tomcat日志显示下面的警告。
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
LDAP配置位于应用程序的context.xml文件中:
<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" />
我在几个论坛上发现了有关此问题的帖子,但似乎没有人找到解决方案。
答案 0 :(得分:6)
我能够弄清楚警告的原因,也找到摆脱它的方法。
警告的原因是LDAP服务器正在关闭所有空闲超过5分钟的连接。 LDAP服务器管理员告诉我,建议在每次登录请求后立即关闭连接,因为可用句柄的数量是有限的。但是,Tomcat的JNDIRealm没有提供配置方法,所以我通过扩展JNDIRealm类并覆盖authenticate(..)方法解决了这个问题。所有需要做的就是在每次身份验证请求和警告消失后关闭与LDAP服务器的连接。
请注意,包必须与JNDIRealm类相同,否则无法访问上下文变量。
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;
}
}
生成的jar需要放在Tomcat的lib文件夹下,并将应用程序的context.xml中的className更改为org.apache.catalina.realm.CustomJNDIRealm。然后重新启动Tomcat,就是这样。
<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" />
答案 1 :(得分:2)
我正在回答,因为这是我目前的研究课题,因为我们目前正在扩展JNDIRealm以满足我们的需求。
领域将在警告后重试,因此建议的补丁只是美化日志文件。更高版本的tomcat(7.0.45 iirc)将美化logmessage以表明已完成重试尝试。
如果你想让领域每次都使用全新的连接进行身份验证,那么使用这个类就足够了(我没有测试过这个实现,但如果我们的领域已经完成了):
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);
}
}
答案 2 :(得分:1)
LDAP服务器在一段时间后断开空闲连接,即没有请求传输的空闲连接。
答案 3 :(得分:0)
基本上添加keepaliveTimeout
来覆盖大约5分钟的连接超时,解决了我的方案中的问题,即keepaliveTimeout ="-1"
文件中的server.xml
属性连接器元素
keepAliveTimeout="-1"