spring-ldap学习(二)

本文继续探讨spring-ldap,重点在于使用spring data风格的编码,使得操作LDAP更简洁。内容包括目录创建、对象类概念解析、model类定义、查询接口的使用以及遇到的schema异常问题和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇介绍了搭建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" />

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值