此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring LDAP 3.2.11spring-doc.cadn.net.cn

简化属性访问和作DirContextAdapter

Java LDAP API 的一个鲜为人知(也可能被低估了)的功能是能够注册DirObjectFactory从找到的 LDAP 条目自动创建对象。 Spring LDAP 利用此功能返回DirContextAdapter实例。spring-doc.cadn.net.cn

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

搜索和查找方式ContextMapper

每当在 LDAP 树中找到一个条目时,Spring LDAP 都会使用其属性和专有名称(DN)来构造一个DirContextAdapter. 这样,我们就可以使用ContextMapper而不是AttributesMapper转换找到的值,如下所示:spring-doc.cadn.net.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());
   }
}

如前面的示例所示,我们可以直接按名称检索属性值,而无需通过AttributesAttribute类。 这在使用多值属性时特别有用。 从多值属性中提取值通常需要遍历NamingEnumerationof 属性值Attributes实现。DirContextAdapter为您执行此作 在getStringAttributes()getObjectAttributes()方法。 以下示例使用getStringAttributes方法:spring-doc.cadn.net.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 提供了ContextMapperAbstractContextMapper. 此实现会自动处理提供的Object参数设置为DirContexOperations. 用AbstractContextMapperPersonContextMapper因此,前面显示的内容可以重写如下:spring-doc.cadn.net.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

` 虽然在提取属性值时很有用,但DirContextAdapter在管理细节方面更加强大 参与添加和更新数据。spring-doc.cadn.net.cn

使用 添加数据DirContextAdapter

以下示例使用DirContextAdapter要实现create添加数据中介绍的 repository 方法:spring-doc.cadn.net.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();
   }
}

请注意,我们使用DirContextAdapterinstance 作为第二个要绑定的参数,它应该是Context. 第三个参数是null,因为我们没有明确指定 attributes。spring-doc.cadn.net.cn

另请注意setAttributeValues()方法,在设置objectclass属性值。 这objectclassattribute 是多值。类似于提取多值属性数据的麻烦, 构建多值属性是一项繁琐而冗长的工作。通过使用setAttributeValues()方法,您可以拥有DirContextAdapter为您处理这项工作。spring-doc.cadn.net.cn

使用 更新数据DirContextAdapter

我们之前看到过使用modifyAttributes是推荐的方法,但这样做需要我们执行 计算属性修改和构造的任务ModificationItem数组。DirContextAdapter可以替我们做这一切,如下所示:spring-doc.cadn.net.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();
   }
}

调用SearchSpec#toEntry,则结果为DirContextAdapter实例。 虽然lookupmethod 返回一个Object,toEntry自动将返回值转换为DirContextOperations(该接口DirContextAdapter实施)。spring-doc.cadn.net.cn

请注意,我们在LdapTemplate#createLdapTemplate#update方法。此代码从 domain 对象映射到上下文。可以将其提取到单独的方法中,如下所示:spring-doc.cadn.net.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.cadn.net.cn

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

从 Spring LDAP 2.0 开始,提供javax.naming.Name实例添加到属性修改方法中,使得DirContextAdapter在计算属性修改时使用可分辨名称相等性。如果我们将前面的示例修改为ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))它不会呈现修改,如下例所示:spring-doc.cadn.net.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();
   }
}

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

A 完整的PersonRepository

为了说明 Spring LDAP 和DirContextAdapter中,以下示例显示了一个完整的PersonLDAP 的存储库实现:spring-doc.cadn.net.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;
      }
   }
}
In several cases, the Distinguished Name (DN) of an object is constructed by using properties of the object. In the preceding example, the country, company and full name of the Person are used in the DN, which means that updating any of these properties actually requires moving the entry in the LDAP tree by using the rename() operation in addition to updating the Attribute values. Since this is highly implementation-specific, this is something you need to keep track of yourself, either by disallowing the user to change these properties or performing the rename() operation in your update() method if needed. Note that, by using Object-Directory Mapping (ODM), the library can automatically handle this for you if you annotate your domain classes appropriately.