【问题标题】:Tomcat 7.0.14 LDAP authenticationTomcat 7.0.14 LDAP 认证
【发布时间】:2012-06-06 09:53:22
【问题描述】:

我有一个在 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" />  

我在几个论坛上找到了有关此问题的帖子,但似乎没有人找到解决方案。

【问题讨论】:

  • 可能由于某种原因,LDAP 服务器关闭了 tomcat 与 LDAP 服务器的连接。专业品质的 LDAP 服务器可以关闭连接,原因有 1) 不活动 2) 操作过多 3) 连接时间过长或其他原因。请咨询 LDAP 服务器管理员,了解他们关于断开现有连接的政策。
  • 感谢您的提示!我联系了 LDAP 服务器管理员,空闲连接的超时时间为 3 分钟,这解释了警告。现在我应该想办法摆脱它。

标签: authentication tomcat ldap


【解决方案1】:

我能够找出警告的原因以及摆脱它的方法。

警告的原因是 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" /> 

【讨论】:

    【解决方案2】:

    我正在回答,因为这是我当前的研究主题,因为我们目前扩展了 JNDIRealm 以满足我们的需求。

    领域将在警告后重试,因此建议的补丁只是美化日志文件。更高版本的 tomcat (7.0.45 iirc) 将美化日志消息以明确表示已完成重试尝试。

    如果你想让领域每次都使用新连接进行身份验证,使用这个类就足够了(我还没有测试过这个实现,但如果我们的领域完成了):

    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);
    
      }
    
    
    }
    

    【讨论】:

      【解决方案3】:

      LDAP 服务器在一定时间后断开已经空闲的空闲连接,即没有请求传输。

      【讨论】:

        【解决方案4】:

        基本上添加一个keepaliveTimeout 来覆盖大约5 分钟的连接超时解决了我的场景中的问题,即server.xml 文件中的连接器元素的keepaliveTimeout ="-1" 属性

        keepAliveTimeout="-1"
        

        【讨论】:

          猜你喜欢
          • 2013-02-14
          • 1970-01-01
          • 2012-10-09
          • 2013-01-26
          • 2012-06-27
          • 2016-08-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多