简化属性访问和操作DirContextAdapter

Java LDAP API 的一个鲜为人知(可能被低估了)功能是能够注册 以从找到的 LDAP 条目自动创建对象。 Spring LDAP 利用此功能在某些搜索和查找操作中返回DirContextAdapter实例。DirObjectFactoryspring-doc.cn

DirContextAdapter是处理 LDAP 属性的有用工具,尤其是在添加或修改数据时。spring-doc.cn

搜索和查找方式ContextMapper

每当在 LDAP 树中找到条目时,Spring LDAP 都会使用其属性和专有名称 (DN) 来构造一个 . 这允许我们使用 ContextMapper 而不是 来转换找到的值,如下所示:DirContextAdapterAttributesMapperspring-doc.cn

示例 1.使用 ContextMapper 进行搜索
public class PersonRepoImpl implements PersonRepo {
   ...
   private static class PersonContextMapper implements ContextMapper {
      public Object mapFromContext(Object ctx) {
         DirContextAdapter context = (DirContextAdapter)ctx;
         Person p = new Person();
         p.setFullName(context.getStringAttribute("cn"));
         p.setLastName(context.getStringAttribute("sn"));
         p.setDescription(context.getStringAttribute("description"));
         return p;
      }
   }

   public Person findByPrimaryKey(
      String name, String company, String country) {
      Name dn = buildDn(name, company, country);
      return ldapClient.search().name(dn).toObject(new PersonContextMapper());
   }
}

如前面的示例所示,我们可以直接按名称检索属性值,而无需遍历 and 类。 这在使用多值属性时特别有用。 从多值属性中提取值通常需要遍历从实现返回的属性值。 为您执行此操作 在 getStringAttributes()getObjectAttributes() 方法中。 以下示例使用该方法:AttributesAttributeNamingEnumerationAttributesDirContextAdaptergetStringAttributesspring-doc.cn

示例 2.使用 获取多值属性值getStringAttributes()
private static class PersonContextMapper implements ContextMapper {
   public Object mapFromContext(Object ctx) {
      DirContextAdapter context = (DirContextAdapter)ctx;
      Person p = new Person();
      p.setFullName(context.getStringAttribute("cn"));
      p.setLastName(context.getStringAttribute("sn"));
      p.setDescription(context.getStringAttribute("description"));
      // The roleNames property of Person is an String array
      p.setRoleNames(context.getStringAttributes("roleNames"));
      return p;
   }
}

AbstractContextMapper

Spring LDAP 提供了一个抽象的基本实现,称为AbstractContextMapper。 此实现会自动将提供的参数转换为 。 因此,使用 前面显示的 可以重写如下:ContextMapperObjectDirContexOperationsAbstractContextMapperPersonContextMapperspring-doc.cn

例 3.使用AbstractContextMapper
private static class PersonContextMapper extends AbstractContextMapper {
  public Object doMapFromContext(DirContextOperations ctx) {
     Person p = new Person();
     p.setFullName(ctx.getStringAttribute("cn"));
     p.setLastName(ctx.getStringAttribute("sn"));
     p.setDescription(ctx.getStringAttribute("description"));
     return p;
  }
}

使用 添加和更新数据DirContextAdapter

` 虽然在提取属性值时很有用,但在管理详细信息时更强大 参与添加和更新数据。DirContextAdapterspring-doc.cn

使用 添加数据DirContextAdapter

以下示例用于实现 添加数据 中介绍的存储库方法的改进实现:DirContextAdaptercreatespring-doc.cn

示例 4.绑定方式DirContextAdapter
public class PersonRepoImpl implements PersonRepo {
   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = new DirContextAdapter(dn);

      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", p.getFullname());
      context.setAttributeValue("sn", p.getLastname());
      context.setAttributeValue("description", p.getDescription());

      ldapClient.bind(dn).object(context).execute();
   }
}

请注意,我们使用实例作为 bind 的第二个参数,它应该是一个 . 第三个参数是 ,因为我们没有明确指定属性。DirContextAdapterContextnullspring-doc.cn

另请注意在设置属性值时 method 的使用。 该属性是多值。类似于提取多值属性数据的麻烦, 构建多值属性是一项繁琐而冗长的工作。通过使用该方法,您可以为您处理该工作。setAttributeValues()objectclassobjectclasssetAttributeValues()DirContextAdapterspring-doc.cn

使用 更新数据DirContextAdapter

我们之前看到,使用 using 进行更新是推荐的方法,但这样做需要我们执行 计算属性修改并相应地构造数组的任务。 可以替我们做这一切,如下所示:modifyAttributesModificationItemDirContextAdapterspring-doc.cn

例 5.更新 usingDirContextAdapter
public class PersonRepoImpl implements PersonRepo {
   ...
   public void update(Person p) {
      Name dn = buildDn(p);
      DirContextOperations context = ldapClient.search().name(dn).toEntry();

      context.setAttributeValue("cn", p.getFullname());
      context.setAttributeValue("sn", p.getLastname());
      context.setAttributeValue("description", p.getDescription());

      ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
   }
}

调用 时,结果默认为实例。 当该方法返回 an 时,会自动将返回值强制转换为 a (实现的接口)。SearchSpec#toEntryDirContextAdapterlookupObjecttoEntryDirContextOperationsDirContextAdapterspring-doc.cn

请注意,我们在 and 方法中有重复的代码。此代码从 domain 对象映射到上下文。可以将其提取到单独的方法中,如下所示:LdapTemplate#createLdapTemplate#updatespring-doc.cn

例 6.使用 DirContextAdapter 进行添加和修改
public class PersonRepoImpl implements PersonRepo {
   private LdapClient ldapClient;

   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = new DirContextAdapter(dn);

      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      mapToContext(p, context);
      ldapClient.bind(dn).object(context).execute();
   }

   public void update(Person p) {
      Name dn = buildDn(p);
      DirContextOperations context = ldapClient.search().name(dn).toEntry();
      mapToContext(person, context);
      ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
   }

   protected void mapToContext (Person p, DirContextOperations context) {
      context.setAttributeValue("cn", p.getFullName());
      context.setAttributeValue("sn", p.getLastName());
      context.setAttributeValue("description", p.getDescription());
   }
}

DirContextAdapter和作为属性值的可分辨名称

在 LDAP 中管理安全组时,通常具有表示 可分辨名称。由于可分辨名称相等性不同于字符串相等性(例如,空格和大小写差异 在可分辨名称相等中被忽略),使用字符串相等计算属性修改无法按预期工作。spring-doc.cn

例如,如果一个属性的值为 ,并且我们调用 , 该属性现在被认为有两个值,即使字符串实际上表示相同 distinguished name 的 Distinguished Name。membercn=John Doe,ou=Peoplectx.addAttributeValue("member", "CN=John Doe, OU=People")spring-doc.cn

从 Spring LDAP 2.0 开始,向属性修改方法提供实例可以在计算属性修改时使用可分辨名称相等性。如果我们将前面的示例修改为 be ,它不会呈现修改,如下例所示:javax.naming.NameDirContextAdapterctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))spring-doc.cn

例 7.使用 DirContextAdapter 修改组成员资格
public class GroupRepo implements BaseLdapNameAware {
    private LdapClient ldapClient;
    private LdapName baseLdapPath;

    public void setLdapClient(LdapClient ldapClient) {
        this.ldapClient = ldapClient;
    }

    public void setBaseLdapPath(LdapName baseLdapPath) {
        this.setBaseLdapPath(baseLdapPath);
    }

    public void addMemberToGroup(String groupName, Person p) {
        Name groupDn = buildGroupDn(groupName);
        Name userDn = buildPersonDn(
            person.getFullname(),
            person.getCompany(),
            person.getCountry());

        DirContextOperation ctx = ldapClient.search().name(groupDn).toEntry();
        ctx.addAttributeValue("member", userDn);

        ldapClient.modify(groupDn).attributes(ctx.getModificationItems()).execute();
    }

    public void removeMemberFromGroup(String groupName, Person p) {
        Name groupDn = buildGroupDn(String groupName);
        Name userDn = buildPersonDn(
            person.getFullname(),
            person.getCompany(),
            person.getCountry());

        DirContextOperation ctx = ldapClient.search().name(groupDn).toEntry();
        ctx.removeAttributeValue("member", userDn);

        ldapClient.modify(groupDn).attributes(ctx.getModificationItems()).execute();
    }

    private Name buildGroupDn(String groupName) {
        return LdapNameBuilder.newInstance("ou=Groups")
            .add("cn", groupName).build();
    }

    private Name buildPersonDn(String fullname, String company, String country) {
        return LdapNameBuilder.newInstance(baseLdapPath)
            .add("c", country)
            .add("ou", company)
            .add("cn", fullname)
            .build();
   }
}

在前面的示例中,我们实现了获取基本 LDAP 路径,如获取对基本 LDAP 路径的引用中所述。 这是必需的,因为作为成员属性值的可分辨名称必须始终是目录根的绝对名称。BaseLdapNameAwarespring-doc.cn

完整的类PersonRepository

为了说明 Spring LDAP 和 的有用性,以下示例显示了 LDAP 的完整存储库实现:DirContextAdapterPersonspring-doc.cn

import java.util.List;

import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;

import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;

import static org.springframework.ldap.query.LdapQueryBuilder.query;

public class PersonRepoImpl implements PersonRepo {
   private LdapClient ldapClient;

   public void setLdapClient(LdapClient ldapClient) {
      this.ldapClient = ldapClient;
   }

   public void create(Person person) {
      DirContextAdapter context = new DirContextAdapter(buildDn(person));
      mapToContext(person, context);
      ldapClient.bind(context.getDn()).object(context).execute();
   }

   public void update(Person person) {
      Name dn = buildDn(person);
      DirContextOperations context = ldapClient.lookupContext(dn);
      mapToContext(person, context);
      ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
   }

   public void delete(Person person) {
      ldapClient.unbind(buildDn(person)).execute();
   }

   public Person findByPrimaryKey(String name, String company, String country) {
      Name dn = buildDn(name, company, country);
      return ldapClient.search().name(dn).toObject(getContextMapper());
   }

   public List<Person> findByName(String name) {
      LdapQuery query = query()
         .where("objectclass").is("person")
         .and("cn").whitespaceWildcardsLike("name");

      return ldapClient.search().query(query).toList(getContextMapper());
   }

   public List<Person> findAll() {
      EqualsFilter filter = new EqualsFilter("objectclass", "person");
      return ldapClient.search().query((query) -> query.filter(filter)).toList(getContextMapper());
   }

   protected ContextMapper getContextMapper() {
      return new PersonContextMapper();
   }

   protected Name buildDn(Person person) {
      return buildDn(person.getFullname(), person.getCompany(), person.getCountry());
   }

   protected Name buildDn(String fullname, String company, String country) {
      return LdapNameBuilder.newInstance()
        .add("c", country)
        .add("ou", company)
        .add("cn", fullname)
        .build();
   }

   protected void mapToContext(Person person, DirContextOperations context) {
      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", person.getFullName());
      context.setAttributeValue("sn", person.getLastName());
      context.setAttributeValue("description", person.getDescription());
   }

   private static class PersonContextMapper extends AbstractContextMapper<Person> {
      public Person doMapFromContext(DirContextOperations context) {
         Person person = new Person();
         person.setFullName(context.getStringAttribute("cn"));
         person.setLastName(context.getStringAttribute("sn"));
         person.setDescription(context.getStringAttribute("description"));
         return person;
      }
   }
}
在一些情况下,对象的可分辨名称 (DN) 是使用对象的属性构造的。 在前面的示例中,DN 中使用了 country、company 和 full name ,这意味着更新这些属性中的任何一个实际上除了更新值之外,还需要使用操作来移动 LDAP 树中的条目。 由于这是高度特定于实现的,因此您需要自行跟踪,要么禁止用户更改这些属性,要么根据需要在您的方法中执行操作。 请注意,通过使用 Object-Directory Mapping (ODM),如果您适当地注释了域类,该库可以自动为您处理此问题。Personrename()Attributerename()update()