如何配置 ldaptive 以使用连接池 (jaas)

How to configure ldaptive to use connection pooling (jaas)

我们有一个应用程序使用 ldap 来验证用户。我们使用 https://www.ldaptive.org/ 作为我们的 ldap 客户端,我们通过 jaas 登录配置文件对其进行配置。

这是我们的 jaas 登录配置文件的示例:

ourApplication {
  org.ldaptive.jaas.LdapLoginModule required
    storePass="true"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=People,dc=example,dc=com"
    useStartTLS="false"
    bindDn="uid=admin,ou=People,dc=example,dc=com"
    bindCredential="password"
    userFilter="(uid={user})";
  org.ldaptive.jaas.LdapRoleAuthorizationModule required
    useFirstPass="true"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=Roles,dc=example,dc=com"
    roleFilter="(member={dn})"
    roleAttribute="cn";
};

这很好用。随着时间的推移,我们开始出现错误,类似于此处描述的错误:

https://confluence.atlassian.com/confkb/ldap-queries-fail-with-address-already-in-use-error-222201829.html

万一 link 变坏,来自 confluence 用户的投诉称用户有时无法通过 ldap 服务器进行身份验证。报告了这样的错误:

2010-08-10 13:04:18,277 ERROR [http-80-8] [user.impl.ldap.LDAPUserManagerReadOnly] getUser Error retrieving user: 'Husein.Alatas' from LDAP.
 -- url: /display/Test | userName: Husein.Alatas | referer: http://confluence/display/dashboard/Home | action: notpermitted
com.atlassian.user.impl.ldap.repository.LdapConnectionFailedException: javax.naming.CommunicationException: ldap.atlassian.com:389 [Root exception is java.net.BindException: Address already in use: connect]
    at com.atlassian.user.impl.ldap.repository.DefaultLdapContextFactory.getLDAPContext(DefaultLdapContextFactory.java:93)
    at com.atlassian.user.impl.ldap.search.DefaultLDAPUserAdaptor.search(DefaultLDAPUserAdaptor.java:70)
    at com.atlassian.user.impl.ldap.search.DefaultLDAPUserAdaptor.search(DefaultLDAPUserAdaptor.java:54)
    at com.atlassian.user.impl.ldap.LDAPUserManagerReadOnly.getUser(LDAPUserManagerReadOnly.java:70)
    at com.atlassian.user.impl.delegation.DelegatingUserManager.getUser(DelegatingUserManager.java:68)
    at bucket.user.DefaultUserAccessor.getUser(DefaultUserAccessor.java:146)
...
Caused by: javax.naming.CommunicationException: ldap.atlassian.com:389 [Root exception is java.net.BindException: Address already in use: connect]
    at com.sun.jndi.ldap.Connection.<init>(Connection.java:200)
    at com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:118)
    at com.sun.jndi.ldap.LdapClientFactory.createPooledConnection(LdapClientFactory.java:46)
    at com.sun.jndi.ldap.pool.Connections.getOrCreateConnection(Connections.java:185)
...
Caused by: java.net.BindException: Address already in use: connect
    at java.net.PlainSocketImpl.socketConnect(Native Method)
...

我们遇到了类似的通信和绑定错误。

上面的 link 建议 运行 宁

netstat -na

我们的应用程序服务器上的命令,以查看是否有很多打开的连接到 ldap 服务器。当我这样做时,我看到数百行如下所示:

TCP    129.135.249.138:65525  129.135.28.18:10389    ESTABLISHED

其中左边地址是我们的server:port,右边是ldap server:port。最终,左边的端口变成了65535,这可能是导致无法认证的原因:所有端口都被占用,所以无法通信。

上面的atlassian link 指向ldaptive jaas配置页面的例子,如何为ldaptive配置连接池:

https://www.ldaptive.org/docs/guide/jaas.html

这里是 ldaptive 给出的配置 ldaptive 以通过 jaas 配置使用连接池的示例:

ldaptive {
  org.ldaptive.jaas.LdapLoginModule required
    ldapUrl="ldap://directory.ldaptive.org:389"
    baseDn="ou=people,dc=ldaptive,dc=org"
    bindDn="cn=priviledged_user,ou=services,dc=vt,dc=edu"
    bindCredential="notarealpassword"
    useStartTLS="true"
    userFilter="(uid={user})"
    userRoleAttribute="eduPersonAffiliation"
    dnResolver="org.ldaptive.auth.SearchDnResolver"
    authenticationHandler="org.ldaptive.auth.SimpleBindAuthenticationHandler"
    connectionFactory="org.ldaptive.PooledConnectionFactory"
    cacheId="ldaptive-pooled";
};

我无法让它工作。首先,我在示例中发现了几个错误:

  1. ldaptive 中没有 SimpleBindAuthenticationHandler class。它 似乎最有可能 class 应该是 BindAuthenticationHandler.
  2. PooledConnectionFactory 的完全限定 class 名称应该是: org.ldaptive.pool.PooledConnectionFactory

进行这些更改后,我的 jaas 配置文件如下所示:

ourApplication {
  org.ldaptive.jaas.LdapLoginModule required
    storePass="true"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=People,dc=example,dc=com"
    useStartTLS="false"
    bindDn="uid=admin,ou=People,dc=example,dc=com"
    bindCredential="password"
    userFilter="(uid={user})"
    dnResolver="org.ldaptive.auth.SearchDnResolver"
    authenticationHandler="org.ldaptive.auth.BindAuthenticationHandler"
    connectionFactory="org.ldaptive.pool.PooledConnectionFactory"
    cacheId="ldaptive-pooled";
  org.ldaptive.jaas.LdapRoleAuthorizationModule required
    useFirstPass="true"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=Roles,dc=example,dc=com"
    roleFilter="(member={dn})"
    roleAttribute="cn";
};

现在,当我 运行 我们的应用程序并尝试进行身份验证时,我在我们的日志文件中看到这个错误:

2019-09-12 14:45:10,229 ERROR (ajp-nio-8009-exec-3)[org.ldaptive.props.AbstractPropertyInvoker] Error invoking public void org.ldaptive.auth.SearchDnResolver.setConnectionFactory(org.ldaptive.ConnectionFactory), on [org.ldaptive.auth.SearchDnResolver@1036972035::factory=null, baseDn=, userFilter=null, userFilterParameters=null, allowMultipleDns=false, subtreeSearch=false, derefAliases=null, referralHandler=null], with params org.ldaptive.pool.PooledConnectionFactory

因此,ldaptive 似乎无法使用我定义的池配置,主要是根据他们的示例。

有人像这样使用 ldaptive(通过 jaas 配置并使用池化)吗?有没有人足够了解 ldaptive 来猜测这里出了什么问题以及如何正确配置它?

我想我还应该问,ldaptive 中的池是否应该解决我通过 netstat -na 看到的端口明显耗尽的问题?

更新:

我能够在我的 jaas 配置文件中使用池化配置:

ourApplication {
  org.ldaptive.jaas.LdapLoginModule required
    storePass="true"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=People,dc=example,dc=com"
    useStartTLS="false"
    bindDn="uid=admin,ou=People,dc=example,dc=com"
    bindCredential="password"
    userFilter="(uid={user})"
    dnResolver="org.ldaptive.auth.PooledSearchDnResolver"
    authenticationHandler="org.ldaptive.auth.PooledBindAuthenticationHandler"
    pruneStrategy="org.ldaptive.pool.IdlePruneStrategy{{prunePeriod=PT3M}{idleTime=PT6M}}"
    cacheId="ldaptive-users-pooled";
  org.ldaptive.jaas.LdapRoleAuthorizationModule required
    useFirstPass="true"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=Roles,dc=example,dc=com"
    roleFilter="(member={dn})"
    roleAttribute="cn"
    dnResolver="org.ldaptive.auth.PooledSearchDnResolver"
    authenticationHandler="org.ldaptive.auth.PooledBindAuthenticationHandler"
    pruneStrategy="org.ldaptive.pool.IdlePruneStrategy{{prunePeriod=PT3M}{idleTime=PT6M}}"
    cacheId="ldaptive-roles-pooled";
};

这似乎确实有所不同,但我仍然看到(在我看来)在我们的应用程序服务器和 ldap 服务器之间保持打开状态的连接太多。 这是 netstat -na 输出的示例:

  TCP    129.135.28.210:61285   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61288   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61290   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61292   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61294   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61299   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61301   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61303   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61309   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61311   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61313   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61317   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61319   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61323   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61336   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61338   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61340   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61342   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61344   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61346   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61348   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61350   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61353   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61355   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61359   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61374   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61376   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61378   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61380   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61383   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61385   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61389   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61396   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61398   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61400   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61402   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61404   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61408   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61416   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61419   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61421   129.135.28.18:10389    ESTABLISHED
  TCP    129.135.28.210:61423   129.135.28.18:10389    ESTABLISHED

当我第一次开始调查这个问题时,几乎所有在应用程序服务器端使用的端口都是连续的。现在看来,顶多是每隔一个端口都在使用,甚至还有2、3、4等端口的空缺。请注意,此示例是在上次 ldap activity.

后将近两个小时拍摄的

为什么这些连接在 ldap activity 停止后还保持这么久? pooling/pruning 配置是否应该做更多以减少连接数?我猜它会比现在更有帮助。

请注意,ldaptive 文档正在过渡到版本 2。 这就是文档似乎不同步的原因。 可以在 http://www.ldaptive.org/v1/

找到版本 1 文档

你的问题的原因是这个错误: https://bugs.openjdk.java.net/browse/JDK-8217606

目前的解决方案是使用 UnboundID 提供程序。更新您的 JAAS 配置并将 ldaptive-unboundid and unboundid jar 添加到您的类路径中。

ourApplication {
  org.ldaptive.jaas.LdapLoginModule required
    storePass="true"
    provider="org.ldaptive.provider.unboundid.UnboundIDProvider"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=People,dc=example,dc=com"
    useStartTLS="false"
    bindDn="uid=admin,ou=People,dc=example,dc=com"
    bindCredential="password"
    userFilter="(uid={user})";
  org.ldaptive.jaas.LdapRoleAuthorizationModule required
    useFirstPass="true"
    provider="org.ldaptive.provider.unboundid.UnboundIDProvider"
    ldapUrl="ldap://ldapserver:10389"
    baseDn="ou=Roles,dc=example,dc=com"
    roleFilter="(member={dn})"
    roleAttribute="cn";
};