spring-ldap学习(二)

来源:互联网 发布:10月经济数据 统计局 编辑:程序博客网 时间:2024/06/09 04:02

上一篇介绍了搭建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加进去就好

 @Testpublic void initDepartment() {ApplicationContext ac = getapp();OrganizationRepo org = (OrganizationRepo) ac.getBean("organizationRepo");org.addOrganization("Departments", "");org.addOrganization("Groups", "");}@Testpublic 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);@Testpublic 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);}@Testpublic 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" />

这样子的是会被忽略掉。运行代码,结果如下: