上一篇介绍了搭建ldap服务,通过GUI风格的ldapsoft ldap admin tool去连接ldap服务端以及介绍了spring-ldap的增删改查,本文将介绍spring data 式的风格去编码,使代码更加简洁,之前也写过一篇spring data mongodb,点击这里查看 spring data mongodb学习以及为repository提供可扩展的自定义方法
首先我们通过ldapsoft ldap admin tool创建几个目录:
第一步:点击左上角dc=dianrong,dc=com,右键选择New Entry,再选择New Organization Unit,在面板里配置上Departments
第二步:点击创建好的Departments,右键选择New Entry,再选择New Organization Unit,在面板里配置上IT
第三步:点击创建好的IT,右键选择New Entry,再选择New Organization Unit,在面板里配置上PROJECTTHREE
这样目录层次就如下图所示:
同时我们也增加一个ou=Groups,最后目录层次如下
上面这些也是可以在我们代码里面实现:
public void addOrganization(String ou,String parent) {
this.getLdapTemplate().bind(this.getDn(ou,parent), null, this.getAttributes(ou,parent));
}
private Attributes getAttributes(String ou,String parent){
Attributes attrs = new BasicAttributes();
Attribute oattr = new BasicAttribute("objectclass");
oattr.add("top");
oattr.add("OrganizationalUnit");
attrs.put(oattr);
attrs.put("ou",ou);
return attrs;
}
private Name getDn(String ou ,String parent){
Name dn = LdapNameBuilder.newInstance(parent).add("ou", ou).build();
return dn;
}
然后写两个junit加进去就好
@Test
public void initDepartment() {
ApplicationContext ac = getapp();
OrganizationRepo org = (OrganizationRepo) ac
.getBean("organizationRepo");
org.addOrganization("Departments", "");
org.addOrganization("Groups", "");
}
@Test
public void initUnits() {
ApplicationContext ac = getapp();
OrganizationRepo org = (OrganizationRepo) ac
.getBean("organizationRepo");
org.addOrganization("IT","ou=Departments");
org.addOrganization("PROJECTTHREE", "ou=IT,ou=Departments");
}
在我们右边的界面可以看到这些信息,比如:objectClass=top; objectClass=organizationlUnit等等
以上就是初始化的一些配置,为下面介绍spring data ldap做铺垫。
在这里简单说一下objectClass:
LDAP中,一个条目必须包含一个objectClass属性,且需要赋予至少一个值。每一个值将用作一条LDAP条目进行数据存储的模板;模板中包含了一个条目必须被赋值的属性和
可选的属性。
objectClass有着严格的等级之分,最顶层是top和alias。例如,organizationalPerson这个objectClass就隶属于person,而person又隶属于top。
objectClass可分为以下3类:
结构型(Structural):如person和organizationUnit;
辅助型(Auxiliary):如extensibeObject;
抽象型(Abstract):如top,抽象型的objectClass不能直接使用。
在OpenLDAP的schema中定义了很多objectClass,下面列出部分常用的objectClass的名称。
account
alias
dcobject
domain
ipHost
organization
organizationalRole
organizationalUnit
person
organizationalPerson
inetOrgPerson
residentialPerson
posixAccount
posixGroup
不了解这些shcema代码中会可能遇到一些错误,这在后面会介绍。
定义model类(model类里面的注解,在上一篇有介绍可以点击这里查看spring-ldap学习(一):
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.DnAttribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import javax.naming.Name;
import java.util.HashSet;
import java.util.Set;
/**
* Created by drjr on 5/19/17.
*/
@Entry(objectClasses = {"groupOfNames", "top"}, base = "ou=Groups")
public class Group {
@Id
private Name id;
@Attribute(name = "cn")
@DnAttribute(value = "cn", index=1)
private String name;
@Attribute(name = "description")
private String description;
@Attribute(name = "member")
private Set<Name> members = new HashSet<Name>();
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<Name> getMembers() {
return members;
}
public void addMember(Name newMember) {
members.add(newMember);
}
public void removeMember(Name member) {
members.remove(member);
}
public Name getId() {
return id;
}
public void setId(Name id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.DnAttribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import org.springframework.ldap.odm.annotations.Transient;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.Name;
/**
* Created by drjr on 5/19/17.
*/
@Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top"}, base = "ou=Departments")
public final class User {
@Id
private Name id;
@Attribute(name = "cn")
@DnAttribute(value="cn", index=3)
private String fullName;
@Attribute(name = "employeeNumber")
private int employeeNumber;
@Attribute(name = "givenName")
private String firstName;
@Attribute(name = "sn")
private String lastName;
@Attribute(name = "title")
private String title;
@Attribute(name = "mail")
private String email;
@Attribute(name = "telephoneNumber")
private String phone;
@DnAttribute(value="ou", index=2)
@Transient
private String unit;
@DnAttribute(value="ou", index=1)
@Transient
private String department;
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public Name getId() {
return id;
}
public void setId(Name id) {
this.id = id;
}
public void setId(String id) {
this.id = LdapUtils.newLdapName(id);
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getEmployeeNumber() {
return employeeNumber;
}
public void setEmployeeNumber(int employeeNumber) {
this.employeeNumber = employeeNumber;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
定义接口来说明我们要对数据库操作什么
import ldap.domain.User;
import org.springframework.ldap.repository.LdapRepository;
import java.util.List;
/**
* Created by drjr on 5/17/17.
*/
public interface UserRepo extends LdapRepository<User> {
User findByEmployeeNumber(int employeeNumber);
List<User> findByFullNameContains(String name);
}
import ldap.domain.Group;
import org.springframework.ldap.repository.LdapRepository;
/**
* Created by drjr on 5/19/17.
*/
public interface GroupRepo extends LdapRepository<Group> {
Group findByName(String groupName);
}
定义对group操作的扩展类:
public interface GroupRepoExtension {
List<String> getAllGroupNames();
void create(Group group);
}
import ldap.dao.GroupRepoExtension;
import ldap.domain.Group;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.stereotype.Repository;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import java.util.List;
/**
* Created by drjr on 5/19/17.
*/
@Repository("groupRepoExtensionImpl")
public class GroupRepoExtensionImpl implements GroupRepoExtension {
private static final String ADMIN_USER = "cn=system";
@Autowired
private LdapTemplate ldapTemplate;
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
@Override
public List<String> getAllGroupNames() {
LdapQuery query = query().attributes("cn")
.where("objectclass").is("groupOfNames");
return ldapTemplate.search(query, new AttributesMapper<String>() {
@Override
public String mapFromAttributes(Attributes attributes) throws NamingException {
return (String) attributes.get("cn").get();
}
});
}
@Override
public void create(Group group) {
System.out.println("name:" + group.getName());
group.addMember(LdapUtils.newLdapName(ADMIN_USER)); //必须要有member成员,默认增加一个系统成员。
ldapTemplate.create(group);
}
}
service层:
import ldap.dao.GroupRepo;
import ldap.dao.UserRepo;
import ldap.domain.Group;
import ldap.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by drjr on 5/19/17.
*/
@Service("userService")
public class UserService {
@Autowired
private UserRepo userRepo;
public void setUserRepo(UserRepo userRepo) {
this.userRepo = userRepo;
}
@Autowired
private GroupRepo groupRepo;
public void setGroupRepo(GroupRepo groupRepo) {
this.groupRepo = groupRepo;
}
public Iterable<User> findAll() {
return userRepo.findAll();
}
public User findUser(String userId) {
return userRepo.findOne(LdapUtils.newLdapName(userId));
}
public List<User> searchByNameName(String lastName) {
return userRepo.findByFullNameContains(lastName);
}
public User create(User user, String group) {
User savesUser = userRepo.save(user);
Group grp = groupRepo.findOne(LdapUtils.newLdapName(group)); //根据组名ID查询到该组
grp.addMember(savesUser.getId()); //保存用户到组
groupRepo.save(grp);
return savesUser;
}
}
同时需要在applicationContext.xml配置: <ldap:repositories base-package="ldap.dao" />这样上面的dao层才起作用。
spring-data 的意思是,我们需要在接口里告诉spring你要查询什么一个什么样的查询,它便帮你实现了。我们不用自己再去写查询语句,不用再拼接复杂的查询,比如你根据员工id去查询这个人,如上只要在dao层定义:User findByEmployeeNumber(int employeeNumber); 也可以查询一个人的名字是否包含某个字符串,例如上面:List<User> findByFullNameContains(String name);
下面是我写的测试类:
import ldap.dao.GroupRepoExtension;
import ldap.domain.Group;
import ldap.domain.User;
import ldap.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by drjr on 5/19/17.
*/
public class LdapDemoTest {
private final AtomicInteger nextEmployeeNumber = new AtomicInteger(10);
@Test
public void initGroup() {
ApplicationContext apc = new ClassPathXmlApplicationContext("applicationContext.xml");
GroupRepoExtension groupRepo = (GroupRepoExtension) apc.getBean("groupRepoExtensionImpl");
Group grp = new Group();
grp.setName("ROLE_USER");
groupRepo.create(grp);
Group grp2 = new Group();
grp2.setName("BASIC_USER");
groupRepo.create(grp2);
}
@Test
public void addUser() {
ApplicationContext apc = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) apc.getBean("userService");
User user = new User();
user.setFullName("Tony Born");
user.setEmployeeNumber(nextEmployeeNumber.getAndIncrement());
user.setFirstName("Tony");
user.setLastName("Born");
user.setTitle("test");
user.setEmail("qq.com");
user.setPhone("18888886666");
user.setUnit("PROJECTTHREE");
user.setDepartment("IT");
userService.create(user, "cn=ROLE_USER,ou=Groups");
}
}
依次执行两个test之后,会得到如下图所示的结果。
在右边可以看到cn=ROLE_USER的详细信息,里面有两个member,其中一个就是cn=Tony Born,ou=PROJECTTHREE,ou=IT,ou=Depatments,就是我们添加user的时候加到该组中的。
cn=Tony Born信息如下:
最后再提下这个schema的事情,需要注意一下:
上面的User类Entry定义是这样子:
如果改成如下所示,会报异常,org.springframework.ldap.SchemaViolationException: [LDAP: error code 65 - attribute 'mail' not allowed];
因为mail,employeeNumber,givenName,title,sn这些是没有定义的。而是在objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top"}才有定义,导致违反schema的异常。所以要了解schema之后才能在model里面定义属性,不能随便添加。
解释一下base:如果上述Group类不指定base = "ou=Groups",那么添加的组就会出现在dc=dianrong,dc=com目录下面。
对上面代码进行一些改进:
1)让 GroupRepo同时继承LdapRepository<Group>和GroupRepoExtension
2)在UserServcie,增加一个借口,同时修改测试类:
区别就是用GroupRepo来统一接口编程,不过这时候会报错:Caused by: org.springframework.data.mapping.PropertyReferenceException: No property getAllGroupNames found for type Group!
需要将GroupRepoExtensionImpl修改成GroupRepoImpl这种格式才行。
再次运行代码,还会出错:Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'groupRepoImpl' for bean class [ldap.dao.impl.GroupRepoImpl] conflicts with existing, non-compatible bean definition of same name and class [ldap.dao.impl.GroupRepoImpl]
意思是这个groupRepoImpl的bean已经存在了。
因为在applicationContext.xml文件中配置了,两个扫描包导致的。
所以去掉这个@Repository("groupRepoImpl")就可以了,但是在applicationContext.xml配置bean,是不会出现已经存在的情况,应该是自动被忽略掉。
<bean class="ldap.dao.impl.GroupRepoImpl" />
这样子的是会被忽略掉。运行代码,结果如下: