官网:http://www.springframework.org/ldap
官方文档及例子(重要):http://docs.spring.io/spring-ldap/docs/2.1.0.RELEASE/reference/
JAVA文档(重要):http://docs.spring.io/spring-ldap/docs/2.1.0.RELEASE/apidocs/
GitHub(大量例子):https://github.com/spring-projects/spring-ldap
Spring LDAP Reference
2.基本使用
2.1 使用AttributesMapper进行search和lookup
(1)通过search返回一个属性值
1 import static org.springframework.ldap.query.LdapQueryBuilder.query; 2 3 public class PersonRepoImpl implements PersonRepo{ 4 private LdapTemplate ldapTemplate; 5 6 public void setLdapTemplate(LdapTemplate ldapTemplate){ 7 this.ldapTemplate = ldapTemplate; 8 } 9 10 public List<String> getAllPersonNames(){ 11 return ldapTemplate.search({ 12 query().where("objectclass").is("person"), 13 new AttributeMapper<String>(){ 14 public String mapFromAttributes(Attribute attrs)throws NamingException{ 15 return (String) attrs.get("cn").get(); 16 } 17 } 18 } 19 }); 20 } 21 }
(2)通过search返回一个Person对象
1 package com.example.repo; 2 import static org.springframework.ldap.query.LdapQueryBuilder.query; 3 4 public class PersonRepoImpl implements PersonRepo { 5 private LdapTemplate ldapTemplate; 6 ... 7 private class PersonAttributesMapper implements AttributesMapper<Person> { 8 public Person mapFromAttributes(Attributes attrs) throws NamingException { 9 Person person = new Person(); 10 person.setFullName((String)attrs.get("cn").get()); 11 person.setLastName((String)attrs.get("sn").get()); 12 person.setDescription((String)attrs.get("description").get()); 13 return person; 14 } 15 } 16 17 public List<Person> getAllPersons() { 18 return ldapTemplate.search(query() 19 .where("objectclass").is("person"), new PersonAttributesMapper()); 20 } 21 }
(3)通过lookup返回一个Person对象
在ldap中,有两个"查询"概念,search和lookup。search是ldaptemplate对每一个entry进行查询,lookup是通过DN直接找到某个条目。
"Entries in LDAP are uniquely identified by their distinguished name (DN). If you have the DN of an entry, you can retrieve(找回) the entry directly without searching for it. This is called a lookup in Java LDAP."
在下面的lookup代码中,ldap会跳过为AttributesMapper查找属性。
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 private LdapTemplate ldapTemplate; 5 ... 6 public Person findPerson(String dn) { 7 return ldapTemplate.lookup(dn, new PersonAttributesMapper()); 8 } 9 }
2.2 创建LDAP Queries
ldap的search 包含许多参数,比如:
1 Base LDAP path 基本路径(search应该从LDAP树的哪里开始) 2 Search scope 查询范围(search应该进行到LDAP树的哪一层) 3 returned attributes要返回的属性 4 Search filter 查询过滤器
1 package com.example.repo; 2 import static org.springframework.ldap.query.LdapQueryBuilder.query; 3 4 public class PersonRepoImpl implements PersonRepo { 5 private LdapTemplate ldapTemplate; 6 ... 7 public List<String> getPersonNamesByLastName(String lastName) { 8 9 LdapQuery query = query() 10 .base("dc=261consulting,dc=com") 11 .attributes("cn", "sn") //返回的属性 12 .where("objectclass").is("person") 13 .and("sn").is(lastName); 14 15 return ldapTemplate.search(query, new AttributesMapper<String>() { 17 public String mapFromAttributes(Attributes attrs)throws NamingException { 20 return attrs.get("cn").get(); //查询每一个entry的"cn"值 21 } 22 }); 23 } 24 }
2.3 动态创建 Distinguished Names(DN)
为了简化对DN的使用,spring-ldap提供了LdapNameBuilder,和工具类LdapUtils。
假设一个Person有如下的属性:
1 Attribute Name Attribute Value 2 country Sweden 3 company Some Company 4 fullname Some Person
(1)使用 LdapNameBuilder 动态创建 LdapName
1 package com.example.repo; 2 import org.springframework.ldap.support.LdapNameBuilder; 3 import javax.naming.Name; 4 5 public class PersonRepoImpl implements PersonRepo { 6 public static final String BASE_DN = "dc=example,dc=com"; 7 8 protected Name buildDn(Person p) { 9 return LdapNameBuilder.newInstance(BASE_DN) 10 .add("c", p.getCountry()) 11 .add("ou", p.getCompany()) 12 .add("cn", p.getFullname()) 13 .build(); 14 } 15 ...
(2)用 LdapUtils 获取属性值
1 package com.example.repo; 2 import org.springframework.ldap.support.LdapNameBuilder; 3 import javax.naming.Name; 4 public class PersonRepoImpl implements PersonRepo { 5 ... 6 protected Person buildPerson(Name dn, Attributes attrs) { 7 Person person = new Person(); 8 person.setCountry(LdapUtils.getStringValue(dn, "c")); 9 person.setCompany(LdapUtils.getStringValue(dn, "ou")); 10 person.setFullname(LdapUtils.getStringValue(dn, "cn")); 11 // Populate rest of person object using attributes. 12 13 return person; 14 }
2.4 绑定和解绑
在Ldap中,新增与删除叫做绑定和解绑。
2.4.1 新增数据
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 private LdapTemplate ldapTemplate; 5 ... 6 public void create(Person p) { 7 Name dn = buildDn(p); 8 ldapTemplate.bind(dn, null, buildAttributes(p)); 9 } 10 11 private Attributes buildAttributes(Person p) { 12 Attributes attrs = new BasicAttributes(); 13 BasicAttribute ocattr = new BasicAttribute("objectclass"); 14 ocattr.add("top"); 15 ocattr.add("person"); 16 attrs.put(ocattr); 17 attrs.put("cn", "Some Person"); 18 attrs.put("sn", "Person"); 19 return attrs; 20 } 21 }
2.4.2 删除数据
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 private LdapTemplate ldapTemplate; 5 ... 6 public void delete(Person p) { 7 Name dn = buildDn(p); 8 ldapTemplate.unbind(dn); 9 } 10 }
2.4.3 更新数据
(1)使用 rebind 更新数据
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 private LdapTemplate ldapTemplate; 5 ... 6 public void update(Person p) { 7 Name dn = buildDn(p); 8 ldapTemplate.rebind(dn, null, buildAttributes(p)); 9 } 10 }
(2)使用 modifyAttributes 更新数据
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 private LdapTemplate ldapTemplate; 5 ... 6 public void updateDescription(Person p) { 7 Name dn = buildDn(p); 8 Attribute attr = new BasicAttribute("description", p.getDescription()) 9 ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); 10 ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item}); 11 } 12 }
3.简化 Attribute 的获取和 DirContextAdapter 的操作
3.1 介绍
Java LDAP API 可以注册一个DirContextAdapter来自动创建对象。spring-ldap使用了这个特点,在search和lookup中返回DirContextAdapter实例。
3.2 通过ContextMapper来search 和lookup
任何时候,想要在LDAP数据树中查找entry,spring-ldap都会使用这个entry的DN和Attributes来构建一个DirContextAdapter,这使得我们不再需要使用 AttributesMapper,而是使用ContextMapper来对获取的属性值进行转换。
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 ... 5 private static class PersonContextMapper implements ContextMapper { 6 public Object mapFromContext(Object ctx) { 7 DirContextAdapter context = (DirContextAdapter)ctx; 8 Person p = new Person(); 9 p.setFullName(context.getStringAttribute("cn")); 10 p.setLastName(context.getStringAttribute("sn")); 11 p.setDescription(context.getStringAttribute("description")); 12 return p; 13 } 14 } 15 16 public Person findByPrimaryKey(String name, String company, String country) { 18 Name dn = buildDn(name, company, country); 19 return ldapTemplate.lookup(dn, new PersonContextMapper()); 20 } 21 }
这里特别方便的一点是:当属性具有多值时,可以通过getStringAttributes()来获取。
1 private static class PersonContextMapper implements ContextMapper { 2 public Object mapFromContext(Object ctx) { 3 DirContextAdapter context = (DirContextAdapter)ctx; 4 Person p = new Person(); 5 p.setFullName(context.getStringAttribute("cn")); 6 p.setLastName(context.getStringAttribute("sn")); 7 p.setDescription(context.getStringAttribute("description")); 8 // The roleNames property of Person is an String array 9 p.setRoleNames(context.getStringAttributes("roleNames")); 10 return p; 11 } 12 }
3.2.1 AbstactContextMapper
spring-ldap提供了一个ContextMapper的抽象的基础实现类:AbstractContextMapper。自定义的的PersonContextMapper可以这样写:
1 private static class PersonContextMapper extends AbstractContextMapper { 2 public Object doMapFromContext(DirContextOperations ctx) { //ctx没有用到?? 3 Person p = new Person(); 4 p.setFullName(context.getStringAttribute("cn")); 5 p.setLastName(context.getStringAttribute("sn")); 6 p.setDescription(context.getStringAttribute("description")); 7 return p; 8 } 9 }
3.3 使用DirContextAdapter新增和更新数据
注意新增的时候用的是:DirContextAdapter。更新的时候用的是:DirContextOperations。二者的关系:DirContextAdapter实现了DirContextOperations接口。
3.3.1 新增数据
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 ... 5 public void create(Person p) { 6 Name dn = buildDn(p); 7 DirContextAdapter context = new DirContextAdapter(dn); 8 //和获取一样,set也可以有多值 9 context.setAttributeValues("objectclass", new String[] {"top", "person"}); 10 context.setAttributeValue("cn", p.getFullname()); 11 context.setAttributeValue("sn", p.getLastname()); 12 context.setAttributeValue("description", p.getDescription()); 13 14 ldapTemplate.bind(context); 15 } 16 }
3.3.2 更新数据
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 ... 5 public void update(Person p) { 6 Name dn = buildDn(p); 7 DirContextOperations context = ldapTemplate.lookupContext(dn); 8 9 context.setAttributeValue("cn", p.getFullname()); 10 context.setAttributeValue("sn", p.getLastname()); 11 context.setAttributeValue("description", p.getDescription()); 12 13 ldapTemplate.modifyAttributes(context); 14 } 15 }
3.3.3 合并新增和更新数据的代码
从前面两段代码可知,新增和更新有重复的代码,因此合并重复代码,整理如下:
1 package com.example.repo; 2 3 public class PersonRepoImpl implements PersonRepo { 4 private LdapTemplate ldapTemplate; 5 6 ... 7 public void create(Person p) { 8 Name dn = buildDn(p); 9 DirContextAdapter context = new DirContextAdapter(dn); 11 context.setAttributeValues("objectclass", new String[] {"top", "person"}); 12 mapToContext(p, context); 13 ldapTemplate.bind(context); 14 } 15 16 public void update(Person p) { 17 Name dn = buildDn(p); 18 DirContextOperations context = ldapTemplate.lookupContext(dn); 19 mapToContext(person, context); 20 ldapTemplate.modifyAttributes(context); 21 } 22 23 protected void mapToContext (Person p, DirContextOperations context) { 24 context.setAttributeValue("cn", p.getFullName()); 25 context.setAttributeValue("sn", p.getLastName()); 26 context.setAttributeValue("description", p.getDescription()); 27 } 28 }
3.4 DirContextAdapter和作为属性值的DN
When managing security groups in LDAP it is very common to have attribute values that represent distinguished names. Since distinguished name equality differs from String equality (例如,空格和大小写在DN的判等中是无视的), calculating attribute modifications using string equality will not work as expected.
假设一个member属性值为:cn=John Doe,ou=People。如果代码写作如下,会被认为是两个值,实际上它代表了同一个DN。
1 ctx.addAttributeValue("member", "CN=John Doe, OU=People")
要写作如下:
1 ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))
使用DirContextAdapter来修改group membership:
1 public class GroupRepo implements BaseLdapNameAware { 2 private LdapTemplate ldapTemplate; 3 private LdapName baseLdapPath; 4 5 public void setLdapTemplate(LdapTemplate ldapTemplate) { 6 this.ldapTemplate = ldapTemplate; 7 } 8 9 public void setBaseLdapPath(LdapName baseLdapPath) { 10 this.setBaseLdapPath(baseLdapPath); 11 } 12 13 public void addMemberToGroup(String groupName, Person p) { 14 Name groupDn = buildGroupDn(groupName); 15 Name userDn = buildPersonDn(person.getFullname(),person.getCompany(), person.getCountry()); 19 20 DirContextOperation ctx = ldapTemplate.lookupContext(groupDn); 21 ctx.addAttributeValue("member", userDn); 22 23 ldapTemplate.update(ctx); 24 } 25 26 public void removeMemberFromGroup(String groupName, Person p) { 27 Name groupDn = buildGroupDn(String groupName); 28 Name userDn = buildPersonDn(person.getFullname(),person.getCompany(),person.getCountry()); 32 33 DirContextOperation ctx = ldapTemplate.lookupContext(groupDn); 34 ctx.removeAttributeValue("member", userDn); 35 36 ldapTemplate.update(ctx); 37 } 38 39 private Name buildGroupDn(String groupName) { 40 return LdapNameBuilder.newInstance("ou=Groups").add("cn", groupName).build(); 42 } 43 44 private Name buildPersonDn(String fullname, String company, String country) { 45 return LdapNameBuilder.newInstance(baseLdapPath).add("c", country).add("ou", company).add("cn", fullname).build(); 50 } 51 }
3.5 使用spring-ldap和DirContextAdapter的完整代码
1 package com.example.repo; 2 import java.util.List; 3 4 import javax.naming.Name; 5 import javax.naming.NamingException; 6 import javax.naming.directory.Attributes; 7 import javax.naming.ldap.LdapName; 8 9 import org.springframework.ldap.core.AttributesMapper; 10 import org.springframework.ldap.core.ContextMapper; 11 import org.springframework.ldap.core.LdapTemplate; 12 import org.springframework.ldap.core.DirContextAdapter; 13 import org.springframework.ldap.filter.AndFilter; 14 import org.springframework.ldap.filter.EqualsFilter; 15 import org.springframework.ldap.filter.WhitespaceWildcardsFilter; 16 17 import static org.springframework.ldap.query.LdapQueryBuilder.query; 18 19 public class PersonRepoImpl implements PersonRepo { 20 private LdapTemplate ldapTemplate; 21 22 public void setLdapTemplate(LdapTemplate ldapTemplate) { 23 this.ldapTemplate = ldapTemplate; 24 } 25 26 public void create(Person person) { 27 DirContextAdapter context = new DirContextAdapter(buildDn(person)); 28 mapToContext(person, context); 29 ldapTemplate.bind(context); 30 } 31 32 public void update(Person person) { 33 Name dn = buildDn(person); 34 DirContextOperations context = ldapTemplate.lookupContext(dn); 35 mapToContext(person, context); 36 ldapTemplate.modifyAttributes(context); 37 } 38 39 public void delete(Person person) { 40 ldapTemplate.unbind(buildDn(person)); 41 } 42 43 public Person findByPrimaryKey(String name, String company, String country) { 44 Name dn = buildDn(name, company, country); 45 return ldapTemplate.lookup(dn, getContextMapper()); 46 } 47 48 public List findByName(String name) { 49 LdapQuery query = query() 50 .where("objectclass").is("person") 51 .and("cn").whitespaceWildcardsLike("name"); 52 53 return ldapTemplate.search(query, getContextMapper()); 54 } 55 56 public List findAll() { 57 EqualsFilter filter = new EqualsFilter("objectclass", "person"); 58 return ldapTemplate.search(LdapUtils.emptyPath(), filter.encode(), getContextMapper()); 59 } 60 61 protected ContextMapper getContextMapper() { 62 return new PersonContextMapper(); 63 } 64 65 protected Name buildDn(Person person) { 66 return buildDn(person.getFullname(), person.getCompany(), person.getCountry()); 67 } 68 69 protected Name buildDn(String fullname, String company, String country) { 70 return LdapNameBuilder.newInstance() 71 .add("c", country) 72 .add("ou", company) 73 .add("cn", fullname) 74 .build(); 75 } 76 77 protected void mapToContext(Person person, DirContextOperations context) { 78 context.setAttributeValues("objectclass", new String[] {"top", "person"}); 79 context.setAttributeValue("cn", person.getFullName()); 80 context.setAttributeValue("sn", person.getLastName()); 81 context.setAttributeValue("description", person.getDescription()); 82 } 83 84 private static class PersonContextMapper extends AbstractContextMapper<Person> { 85 public Person doMapFromContext(DirContextOperations context) { 86 Person person = new Person(); 87 person.setFullName(context.getStringAttribute("cn")); 88 person.setLastName(context.getStringAttribute("sn")); 89 person.setDescription(context.getStringAttribute("description")); 90 return person; 91 } 92 } 93 }