Spring-由于 JDNI 实现,LDAP 过于严格

Spring-LDAP too strict because of JDNI implementation

再见,

我在使用 Spring-LDAP 时遇到了一个相当奇怪的问题,从本质上讲,它更像是一个 JSNI 问题,而不是 Spring 问题。

问题:

我有以下 LDAP 结构:

dc=company,dc=com,dc=productname
    |-o=TESTORG1
    |     |-ou=UserGroupName
    |     |-ou=users
    |           |-ou=user1@TESTORG1
    |-o=TESTORG2
    |     |-ou=User/Group/Name
    |     |-ou=users
    |           |-ou=user1@TESTORG2

所以我可以使用 LdapTemplate 通过 Spring 非常巧妙地创建这一切。但是,如果我尝试递归删除组织,我 运行 会遇到一些奇怪的问题。

  @Test
  public void createSimpleOrganisationWithSpecialChars() throws NamingException {
    LdapOrganisation originalOrg = new LdapOrganisation("TESTORG2");
    LdapUserGroup userGroup = new LdapUserGroup("User/Group/Name");
    userGroup.addUser(new User("user1",originalOrg ));
    originalOrg.addUserGroup(userGroup);
    dataAccess.updateLdap(originalOrg);
    OrganisationDao org = new OrganisationDao(this.ldapTemplate);
    org.unbind(organisation,true);
  }

解除绑定的实现如下:

    public void unbind(LdapOrganisation organisation, boolean recursive) {
      if(organisation != null) {
        DirContextAdapter context = new DirContextAdapter(buildDn(organisation));
        mapToContext(organisation, context);
        ldapTemplate.unbind(context.getDn(), recursive);
      }
    }

现在,如果我 运行 此代码,我会收到以下错误:

  org.springframework.ldap.InvalidNameException: Invalid name: "ou=User/Group/Name"; nested exception is javax.naming.InvalidNameException: Invalid name: "ou=User/Group/Name"

    at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:136)
    at org.springframework.ldap.support.LdapUtils.newLdapName(LdapUtils.java:416)
    at org.springframework.ldap.core.LdapTemplate.deleteRecursively(LdapTemplate.java:1103)
    at org.springframework.ldap.core.LdapTemplate.executeWithContext(LdapTemplate.java:1074)
    at org.springframework.ldap.core.LdapTemplate.executeWithContext(LdapTemplate.java:817)
    at org.springframework.ldap.core.LdapTemplate.executeReadWrite(LdapTemplate.java:812)
    at org.springframework.ldap.core.LdapTemplate.doUnbindRecursively(LdapTemplate.java:1072)
    at org.springframework.ldap.core.LdapTemplate.unbind(LdapTemplate.java:1036)
    at net.thadir.dataservices.ldap.dao.OrganisationDao.unbind(OrganisationDao.java:108)
    at net.thadir.dataservices.ldapaccess.LdapDataAccessTest.cleanTest(LdapDataAccessTest.java:93)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:239)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=14=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
  Caused by: javax.naming.InvalidNameException: Invalid name: "ou=User/Group/Name"
    at javax.naming.ldap.Rfc2253Parser.parseAttrType(Rfc2253Parser.java:155)
    at javax.naming.ldap.Rfc2253Parser.doParse(Rfc2253Parser.java:108)
    at javax.naming.ldap.Rfc2253Parser.parseDn(Rfc2253Parser.java:70)
    at javax.naming.ldap.LdapName.parse(LdapName.java:785)
    at javax.naming.ldap.LdapName.<init>(LdapName.java:123)
    at org.springframework.ldap.support.LdapUtils.newLdapName(LdapUtils.java:414)
    ... 37 more

现在,如果我只是在测试中进行正常的解除绑定,它只会在用户组上自动解除绑定。没有真正的问题。。但它实际上是一个问题。因为它似乎 Spring 用于递归删除 JDNI 模式(这比 LDAP 允许的更严格)和正常的解除绑定。它完美运行。

现在我真的想让我的递归工作开箱即用。我尝试使用以下代码删除组:

在 OrganisationDoa 中:

  public void unbind(LdapOrganisation organisation, boolean recursive) {
    if(organisation != null) {
      UserGroupDao ugu = new UserGroupDao(ldapTemplate);
      ugu.delete(organisation);
      DirContextAdapter context = new DirContextAdapter(buildDn(organisation));
      mapToContext(organisation, context);
      ldapTemplate.unbind(context.getDn(), recursive);
    }
  }

并且在 UserGroupDao 中:

  public void delete(LdapOrganisation ldapOrganisation, String ldapUserGroup) {
        ldapTemplate.unbind(buildDn(ldapOrganisation.getOrganisationShortname(), ldapUserGroup));
  }

  public void delete(LdapOrganisation ldapOrganisation) {
        for (LdapUserGroup userGroup : ldapOrganisation.getUserGroups()) {
              delete(ldapOrganisation,userGroup.getGroupName());
        }
  }

但这似乎行不通,看到我仍然得到相同的堆栈跟踪(因此尝试以相同的方式进行操作似乎行不通)。

我一直在阅读所有这些 RFC 规范,它们看起来很旧。 LDAP 似乎允许这样做,因为 Spring 似乎将 JDNI 用于我遇到此问题的递归部分。对您预计会损坏的部件进行预清理似乎效果不佳。所以我很想听听一些 suggestions/ideas 如何解决这个问题。

最后我不得不自己实现 org.springframework.ldap.core.LdapTemplate 来解决问题修复如下:

package net.thadir.dataservices.ldap.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;

import javax.naming.Binding;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.directory.DirContext;
import javax.naming.ldap.LdapName;

public class FixedLdapTemplate extends LdapTemplate {
  private static final Logger LOGGER = LoggerFactory.getLogger(FixedLdapTemplate.class);

  public FixedLdapTemplate(LdapContextSource contextSource) {
    super(contextSource);
  }

  /**
   * Delete all subcontexts including the current one recursively.
   *
   * @param ctx The context to use for deleting.
   * @param name The starting point to delete recursively.
   * @throws NamingException if any error occurs
   */
  @Override
  protected void deleteRecursively(DirContext ctx, Name name) {
    NamingEnumeration enumeration = null;
    try {
      enumeration = ctx.listBindings(name);
      while (enumeration.hasMore()) {
        Binding binding = (Binding) enumeration.next();
        String bindingName = binding.getName();
        LdapName childName = org.springframework.ldap.support.LdapUtils.newLdapName(fixJDNIBug(bindingName));
        childName.addAll(0, name);
        deleteRecursively(ctx, childName);
      }
      ctx.unbind(name);
      LOGGER.debug("Entry {} deleted", name);
    }
    catch (javax.naming.NamingException e) {
      throw org.springframework.ldap.support.LdapUtils.convertLdapException(e);
    }
    finally {
      try {
        if (enumeration != null) {
          enumeration.close();
        }
      }
      catch (Exception e) {
        LOGGER.trace("Never mind this", e);
      }
    }
  }

  /**
   * Becouse somhow JDNI things that if you have a binding with some special characters it adds some extra " to the beginnin and the end. We have to remove it.
   *
   * @param bindingName
   * @return
   */
  private static String fixJDNIBug(String bindingName) {
    String result = bindingName;
    if(result.contains("/") && result.startsWith("\"") && result.endsWith("\"")) {
      result = result.substring(1,bindingName.length()-1);
    }
    return result;
  }
}

有问题的错误在过去 10 年里一直是 JDNI 的一部分,所以我希望它很快会在 Spring 中自行修复。因为很遗憾我不得不在那里扩展 类 来做一件小事。