此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring LDAP 3.3.3! |
简化属性访问和作DirContextAdapter
Java LDAP API 的一个鲜为人知且可能被低估的功能是能够注册DirObjectFactory
从找到的 LDAP 条目自动创建对象。
Spring LDAP 利用此功能返回DirContextAdapter
某些搜索和查找作中的实例。
DirContextAdapter
是处理 LDAP 属性的有用工具,尤其是在添加或修改数据时。
搜索和查找使用ContextMapper
每当在 LDAP 树中找到条目时,Spring LDAP 都会使用其属性和可分辨名称 (DN) 来构造DirContextAdapter
.
这允许我们使用ContextMapper
而不是AttributesMapper
转换找到的值,如下所示:
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());
}
}
如前面的示例所示,我们可以直接按名称检索属性值,而无需通过Attributes
和Attribute
类。
这在处理多值属性时特别有用。
从多值属性中提取值通常需要循环NamingEnumeration
的属性值从Attributes
实现。DirContextAdapter
为您做这件事
在getStringAttributes()
或getObjectAttributes()
方法。
以下示例使用getStringAttributes
方法:
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 提供了一个抽象的基础实现ContextMapper
叫AbstractContextMapper
.
此实现会自动处理所提供的Object
参数设置为DirContexOperations
.
用AbstractContextMapper
这PersonContextMapper
因此,前面显示的可以重写如下:
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
在管理细节方面更加强大
参与添加和更新数据。
使用DirContextAdapter
以下示例使用DirContextAdapter
实现改进的create
添加数据中介绍的存储库方法:
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();
}
}
请注意,我们使用DirContextAdapter
instance 作为要绑定的第二个参数,该参数应该是Context
.
第三个参数是null
,因为我们没有显式指定属性。
另请注意setAttributeValues()
方法objectclass
属性值。
这objectclass
属性是多值。与提取多值属性数据的麻烦类似,
构建多值属性是一项繁琐而冗长的工作。通过使用setAttributeValues()
方法,你可以有DirContextAdapter
为您处理这项工作。
使用DirContextAdapter
我们之前看到通过使用modifyAttributes
是推荐的方法,但这样做需要我们执行
计算属性修改和构造的任务ModificationItem
数组。DirContextAdapter
可以为我们做这一切,如下:
DirContextAdapter
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
实例。
虽然lookup
方法返回一个Object
,toEntry
自动将返回值转换为DirContextOperations
(界面DirContextAdapter
工具)。
请注意,我们在LdapTemplate#create
和LdapTemplate#update
方法。此代码从域对象映射到上下文。可以提取到单独的方法,如下所示:
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 中管理安全组时,通常具有表示 尊贵名称。由于可分辨名称相等性与字符串相等性不同(例如,空格和大小写差异 在可分辨名称相等中被忽略),使用字符串相等计算属性修改无法按预期工作。
例如,如果member
属性的值为cn=John Doe,ou=People
我们调用ctx.addAttributeValue("member", "CN=John Doe, OU=People")
,
该属性现在被视为具有两个值,即使字符串实际上表示相同的值
可区分的名称。
从 Spring LDAP 2.0 开始,提供javax.naming.Name
属性修改方法的实例使DirContextAdapter
计算属性修改时使用可分辨名称相等。如果我们将前面的示例修改为ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))
,则不会呈现修改,如以下示例所示:
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 路径的引用中所述。
这是必要的,因为作为成员属性值的可分辨名称必须始终与目录根目录绝对一致。
一个完整的PersonRepository
类
为了说明 Spring LDAP 和DirContextAdapter
,以下示例显示了一个完整的Person
LDAP 的存储库实现:
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) 是使用对象的属性构造的。
在前面的示例中,国家/地区、公司和全名Person 在 DN 中使用,这意味着更新这些属性中的任何一个实际上都需要使用rename() 作除了更新Attribute 值。
由于这是高度特定于实现的,因此您需要跟踪自己,通过禁止用户更改这些属性或执行rename() 作update() 如果需要,可以使用方法。
请注意,通过使用对象目录映射 (ODM),如果您适当地注释域类,库可以自动为您处理此问题。 |