Bootstrap

LDAP基本应用

目录

安装LDAP链接:https://segmentfault.com/a/1190000014683418

转载自 LDAP入门概念阐述 

LDAP编程操作

JNDI连接LDAP服务器

                  Spring LDAP的使用

                  Spring ldap ODM

                  LdapQuery:高级构建查询


安装LDAP链接:https://segmentfault.com/a/1190000014683418

转载自 LDAP入门概念阐述 

  • 首先要先理解什么是LDAP,当时我看了很多解释,也是云里雾里,弄不清楚。在这里给大家稍微捋一捋。
  • 首先LDAP是一种通讯协议,LDAP支持TCP/IP。协议就是标准,并且是抽象的。在这套标准下,AD(Active Directory)是微软出的一套实现。
    那AD是什么呢?暂且把它理解成是个数据库。也有很多人直接把LDAP说成数据库(可以把LDAP理解成存储数据的数据库)。像是其他数据库一样,LDAP也是有client端和server端。server端是用来存放资源,client端用来操作增删改查等操作。
  • 而我们通常说的LDAP是指运行这个数据库的服务器。
  • 可以简单理解AD =LDAP服务器+LDAP应用。

那LDAP这种数据库有什么特殊的呢?

  • 我们知道,像MySQL数据库,数据都是按记录一条条记录存在表中。而LDAP数据库,是树结构的,数据存储在叶子节点上。看看下面的比喻:

假设你要树上的一个苹果(一条记录),你怎么告诉园丁它的位置呢?当然首先要说明是哪一棵树(dc,相当于MYSQL的DB),然后是从树根到那个苹果所经过的所有“分叉”(ou),最后就是这个苹果的名字(uid,相当于MySQL表主键id)。好了!这时我们可以清晰的指明这个苹果的位置了,就是那棵“歪脖树”的东边那个分叉上的靠西边那个分叉的再靠北边的分叉上的半红半绿的……,晕了!你直接爬上去吧!

就这样就可以描述清楚“树结构”上的一条记录了。
说一下LDAP里如何定义一个记录的位置吧。

树(dc=ljheee)
分叉(ou=bei,ou=xi,ou= dong)
苹果(cn=redApple)

好了,redApple的位置出来了:
dn:cn=honglv,ou=bei,ou=xi,ou=dong,dc=ljheee
其中dn标识一条记录,描述了一条数据的详细路径。
咦!有人疑问,为什么ou会有多个值?你想想,从树根到达苹果的位置,可能要经过好几个树杈,所有ou可能有多个值。关于dn后面一长串,分别是cn,ou,dc;中间用逗号隔开。

总结一下LDAP树形数据库如下:
dn :一条记录的详细位置
dc :一条记录所属区域    (哪一颗树)
ou :一条记录所属组织    (哪一个分支)
cn/uid:一条记录的名字/ID   (哪一个苹果名字)
LDAP目录树的最顶部就是根,也就是所谓的“基准DN"。
  • 为什么要用LDAP目录树来存储数据,用MySQL不行吗,为什么非要搞出一个树形的数据库呢?
  • 这是因为用树形结构存储数据,查询效率更高(具体为什么,可以看一下关系型数据库索引的实现原理——B树/B+树)。在某些特定的场景下,使用树形数据库更理想。比如:需要储存大量的数据,而且数据不是经常更改,需要很快速的查找。
  • 把它与传统的关系型数据库相比,LDAP除了快速查找的特点,它还有很多的运用场景,比如域验证等。

LDAP编程操作

  • 我们可以用JDBC操作MySQL数据库,进行对数据的增删改查。同样,LDAP树形数据库,也可以通过JDBC方式;除此之外,还可以用JNDI的方式(更推荐),因为树形可以看做是目录,树结构的枝杈相当于目录的层级。
  • 还有LDAP数据库展示数据也是树形的,如下图是用ApacheDirectoryStudio连接的LDAP服务器:

     

    image.png

可以把ApacheDirectoryStudio看做是连接数据库服务器的界面化的client,相当于Navicat、WorkBench。新建连接,连接数据库服务器的操作类似。

ApacheDirectoryStudio下载地址
http://download.csdn.net/download/ljheee/10145654

JNDI连接LDAP服务器

import org.springframework.beans.factory.annotation.Autowired;
import java.util.Hashtable;
import javax.naming.*;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class LdapJNDI {

    @Autowired
    LdapConfiguration ldapConfiguration;

    public void JNDILookup() {
        String rootFilter = "o=cvte.com,o=isp";
//        String filter = "(&(smart-type=E1)(smart-status=1))";
        String filter = "(&(smart-type=E1)(uid=00012047))";
        String username = "uid=USER_NAME,ou=Authorization,ou=People,o=cc.com,o=isp";//xxx为申请的对接账户
        String password = "PASSW";

        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");//设置连接LDAP的实现工厂
        env.put(Context.PROVIDER_URL, "ldap://172.26.39.77:389/" + rootFilter);// 指定LDAP服务器的主机名和端口号
        env.put(Context.SECURITY_AUTHENTICATION, "simple");//给环境提供认证方法,有SIMPLE、SSL/TLS和SASL
        env.put(Context.SECURITY_PRINCIPAL, username);//指定进入的目录识别名DN
        env.put(Context.SECURITY_CREDENTIALS, password); //进入的目录密码
        env.put("filter",filter);
        DirContext ctx = null;

        try {
            // 得到初始目录环境的一个引用
            ctx = new InitialDirContext(env);

            //The search base entry 'uid=00012047,ou=Internal,ou=People,o=cvte.com,o=isp' does not exist]; remaining name 'uid=00012047,ou=Internal'
//            Attributes attrs = ctx.getAttributes("uid=00012047,ou=Internal,ou=People");//获取到一个人员,


            NamingEnumeration bindings = ctx.listBindings("ou=Internal,ou=People");//列举 内部人员

            while (bindings.hasMore()) {
                Binding bd = (Binding)bindings.next();
                System.out.println(bd.getName() + ": " + bd.getObject());
            }


             /*根据结点的DN来查找它的所有属性, 然后再从属性中得到所有的值,注意一个属性可以有多个值*/
//            for (NamingEnumeration ae = attrs.getAll(); ae.hasMore(); ) {
//                //获取一个属性
//                Attribute attr = (Attribute) ae.next();
//                for (NamingEnumeration ve = attr.getAll(); ve.hasMore(); ) {
//                    System.out.println(String.format("Attribute=%s,Value=%s",attr.getID(),ve.next()) );
//                }
//            }

        } catch (javax.naming.AuthenticationException e) {
            System.out.println("认证失败");
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("认证出错:");
            e.printStackTrace();
        }finally {
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (NamingException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static void main(String[] args) {
        LdapJNDI ldapJNDI = new LdapJNDI();
        ldapJNDI.JNDILookup();

    }

}

最后总结一下LDAP:
1、LDAP的结构用树来表示,而不是用表格。正因为这样,就不能用SQL语句了。
2、LDAP可以很快地得到查询结果,不过在写方面,就慢得多。
3、LDAP提供了静态数据的快速查询方式。
4、Client/server模型,Server 用于存储数据,Client提供操作目录信息树的工具。
5、LDAP是一种开放Internet标准,LDAP协议是跨平台的Interent协议。

 

Spring LDAP的使用

  • Spring LDAP,是Spring的一个组件,实现对LDAP的操作。

  • 在编程操作MySQL时,我们除了用JDBC,可能都会选用一些框架,比如JbdcTemplate。

  • JdbcTemplate的实现是通过传入sql语句和RowMapper,query返回目标列表,或是传入sql和参数,执行update方法。JdbcTemplate的优点是简化了与数据库连接的代码,以及避免了一些常见的错误。

  • 同样的,Spring LDAP框架也提供了类似的特性——LdapTemplate。

  • 优点都是相通的,Spring LdapTemplate的优点是简化了与LDAP交互的代码。

  • 按之前Spring配置JavaBean的方式,在xml文件配置LdapTemplate及其属性值即可,本文将演示使用Springboot 用Java代码的方式定义LdapTemplate,完成Spring ldap连接数据库服务及进行相关操作。

下面是使用Spring-ldap的依赖

<!-- spring ldapTemplate操作 -->
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>ldapbp</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>
  • 先介绍一些Spring-ldap,因为网上有很多教程,在给出的工程依赖中有用spring-ldap的,也有spring-ldap-core的,而且还有版本问题。笔者使用目前最新的spring-ldap-2.3.2.RELEASE。推荐直接使用,这个最新版本。

  • spring-ldap框架,是Spring集成ldap操作的总和,包含spring-ldap-core,spring-ldap-core-tiger,spring-ldap-ldif-core,spring-ldap-odm等jar,而通常我们在工程中只需要引入spring-ldap-core即可,它提供了绝大部分功能。而且截至目前,spring-ldap的<version>2.3.2.RELEASE</version>不在maven的中央仓库,不好获取。但spring-ldap-core在。

  • 另外,Spring LDAP 2.0对jdk版本要求是1.6,并且开始支持ODM,并后来引入Spring ldap pool连接池。

  • 据本人尝试,这些版本之间,变化差异很大。在新版本中可能有些关键的核心类,都会被移动到不同的package下;一些老版本完成的愚钝功能,可能在新版本中有了更好的实现或支持,所以在新版本中,一些“愚钝”实现可能会被移除。

  • 比如LdapTemplate,原先在org.springframework.ldap包,在最新版本被移至core包。在spring-ldap-core的<version>2.0.2.RELEASE</version>版本中支持类似于JPA方式的LdapRepository,但在2.3.2最新版本中,完全被移除,但是新版本增强的LdapTemplate,使得LdapTemplate功能更强大,可以完全替代LdapRepository。

下面是用Java代码的方式定义LdapTemplate,完成用Spring ldap连接LDAP服务器

import com.cvte.csb.sim.ldap.constants.LdapConstans;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.pool.factory.PoolingContextSource;
import org.springframework.ldap.pool.validation.DefaultDirContextValidator;
import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy;
import java.util.HashMap;
import java.util.Map;

/**
 * LDAP 的自动配置类
 *
 * 完成连接 及LdapTemplate生成
 */
@Configuration
public class LdapConfiguration {

    private LdapTemplate ldapTemplate;

    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        Map<String, Object> config = new HashMap();

        contextSource.setUrl(LdapConstans.LDAP_URL);
        contextSource.setBase(LdapConstans.BASE_DC);
        contextSource.setUserDn(LdapConstans.USER_NAME);
        contextSource.setPassword(LdapConstans.PASS_WORD);

        //  解决 乱码 的关键一句
        config.put("java.naming.ldap.attributes.binary", "objectGUID");

        contextSource.setPooled(true);
        contextSource.setBaseEnvironmentProperties(config);
        return contextSource;
    }
 
    @Bean
    public LdapTemplate ldapTemplate() {
        if (null == ldapTemplate)
            ldapTemplate = new LdapTemplate(contextSource());
        return ldapTemplate;
    }

}
  • 完成LdapTemplate的bean定义,是最关键的一步。因为后续的操作,对于LDAP目录树的CRUD操作,全都靠它完成。
  • 通过上面的代码,在IOC容器完成bean的定义,我们在外部就可以注入使用LdapTemplate了。

下面给出用LdapTemplate完成CRUD功能:

import com.cvte.csb.sim.ldap.attribute.LdapDeptAttributeMapper;
import com.cvte.csb.sim.ldap.attribute.LdapUserAttributeMapper;
import com.cvte.csb.sim.ldap.module.dto.LdapUser;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import java.util.List;

public class ConfigTest extends BaseTest {
    @Autowired
    private LdapTemplate ldapTemplate;

    /**
     * 获取所有 内部人员
     * ou=Internal,ou=People
     */
    @Test
    public void listUsers(){
        AndFilter filter = new AndFilter();
        filter.and(new EqualsFilter("objectClass", "person"));

        //查询所有内部人员
        List<LdapUser> users = ldapTemplate.search("ou=Internal,ou=People", filter.encode(), new LdapUserAttributeMapper());
        for (LdapUser user: users ) {
            System.out.println(user);
        }

//        Assert.assertEquals(3056, users.size());
    }

    /**
     * 根据uid 查找单个人员
     */
    @Test
    public void findUser(){

        //uid=00012047,ou=Internal,ou=People,o=cvte.com,o=isp
        DirContextAdapter obj = (DirContextAdapter) ldapTemplate.lookup("uid=00012047,ou=Internal,ou=People");//BASE_DC 不用填
        System.out.println(obj);
    }

    /**
     * 根据部门编号o,查找部门
     */
    @Test
    public void findDept(){
        //o=598b09cb12ab4364864d8ac73ecee00d,ou=Organizations,ou=People,o=cvte.com,o=isp
        DirContextAdapter obj = (DirContextAdapter) ldapTemplate.lookup("o=598b09cb12ab4364864d8ac73ecee00d,ou=Organizations");//BASE_DC 不用填
        System.out.println(obj);
    }

    @Test
    public void listDepts(){
        AndFilter filter = new AndFilter();
        filter.and(new EqualsFilter("objectClass", "organization"));
        //search是根据过滤条件进行查询,第一个参数是父节点的dn,可以为空,不为空时查询效率更高
        List depts = ldapTemplate.search("", filter.encode(), new LdapDeptAttributeMapper());
        System.out.println(depts.size());
//        Assert.assertEquals(3056, depts.size());
    }
}
  • 在ldap中,有两个"查询"概念,search和lookup。search是ldaptemplate对每一个entry进行查询,lookup是通过DN直接找到某个条目。
  • 在Ldap中,新增与删除叫做绑定bind和解绑unBind。这些方法LdapTemplate全部提供,并且还提供各种条件过滤等方法,不如findAll(),list()等。

我们注意到,findAll(),list()肯定是返回一个java.util.List<T>,包括,

//查询所有内部人员
List<LdapUser> users = ldapTemplate.search("ou=Internal,ou=People", filter.encode(), new LdapUserAttributeMapper());

也是返回列表,列表里装的是查询出来的结果。但是上一篇文章用JNDI方式查询出来的是
Attributes attrs = ctx.getAttributes("uid=00012047,ou=Internal,ou=People");//获取到一个人员
Spring-ldap是基于JNDI实现的封装,那是哪里实现的把Attributes转成我们需要的Java Bean对象呢?
答案在new LdapUserAttributeMapper(),这个接口实现了查询结果到对象的转化。

import com.cvte.csb.sim.ldap.module.dto.LdapUser;
import org.springframework.ldap.core.AttributesMapper;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;

/**
 * 将ldap返回的结果,转成指定对象
 */
public class LdapUserAttributeMapper implements AttributesMapper {

    /**
     * 将单个Attributes转成单个对象
     * @param attrs
     * @return
     * @throws NamingException
     */
    public Object mapFromAttributes(Attributes attrs) throws NamingException {
        LdapUser user  = new LdapUser();

        if(attrs.get("uid") != null){
            user.setUsername( attrs.get("uid").get().toString());
        }
        if(attrs.get("cn") != null){
            user.setUserCn( attrs.get("cn").get().toString());
        }
        if(attrs.get("mobile") != null){
            user.setMobile( attrs.get("mobile").get().toString());
        }
        if(attrs.get("mail") != null){
            user.setMail( attrs.get("mail").get().toString());
        }
        if(attrs.get("employeeNumber") != null){
            user.setUserNumber( attrs.get("employeeNumber").get().toString());
        }

        if(attrs.get("type") != null){
            user.setUserType( attrs.get("type").get().toString());
        }
        if(attrs.get("py") != null){
            user.setPinyin(attrs.get("py").get().toString());
        }
        if(attrs.get("alias") != null){
            user.setAlias(attrs.get("alias").get().toString());
        }
        if(attrs.get("departmentNumber") != null){
            user.setDeptId(attrs.get("departmentNumber").get().toString());
        }
        if(attrs.get("departmentName") != null){
            user.setDeptName(attrs.get("departmentName").get().toString());
        }
        if(attrs.get("jobname") != null){
            user.setPositionName(attrs.get("jobname").get().toString());
        }
        if(attrs.get("modifyTimestamp") != null){
            user.setModifyTimestamp(attrs.get("modifyTimestamp").get().toString());
        }
        return user;
    }
}

可以看到转化的过程非常繁琐,无非就是拿JNDI查询到的Attributes,不停的获取属性值,再设置到Java对象中;attrs.get("uid").get().toString()然后set。

那好了,在每次查询的时候,要查询到多少列,在这个AttributesMapper转化方法中就要写多少个,判断及赋值。而且,如果因为业务不同,要查询不同的列,那AttributesMapper接口的实现必须重新写。那有没有支持复用的方式呢?答案是肯定的。下节分享spring ldap ODM , Object-Directory Mapping。

spring-ldap-2.3.2.RELEASE所有jar包下载
http://download.csdn.net/download/ljheee/10150501

Spring-ldap最新版官方文档:
https://docs.spring.io/spring-ldap/docs/2.3.2.RELEASE/reference/

Spring ldap ODM

 

上文讲述了Spring-ldap基本操作,通过定义LdapTemplate这个bean到IOC容器,使用时注入LdapTemplate即可完成对LDAP目录树的CRUD及筛选、过滤等。

  • 但是对于筛选查询出来的内容,JNDI是封装在Attributes中,尽管spring-ldap提供了AttributesMapper接口,让你自己去实现具体的从Attributes转成Java对象的逻辑,但是随着业务变化,对于不同的查询列,转化逻辑必须重新写。

  • 那有没有办法,让查询出来的内容,自动转成我们想要Java对象呢,就像关系型数据库ORM,把从MySQL数据库查询的结果集,自动完成和实体类的映射,执行查询时,直接得到对象列表。

  • spring-ldap同样提供相应的支持(从spring-ldap 2.x版本开始),ODM (Object-Directory Mapping)对象目录映射。不同于MySQL数据库的是,LDAP是目录树,是树结构的数据库。

spring-ldap该框架通过提供和ORM中相似的机制对LDAP相关操作进行封装,主要包括:
1、类比SessionFactory的LdapContextSource;
2、类比HibernateTemplate等的LdapTemplate;
3、伪事务支持,能否与tx框架的TransactionManager混用未知;
4、类比JPA的使用@Entry、@Attribute、@Id标注的注解。

ORM框架,例如Hibernate 或者JPA,使得我们可以使用注解,映射一张表到一个Java实体。Spring-ldap提供相似的功能以完成对LDAP目录树的操作,LdapOperations接口提供了很多类似的方法:

<T> T findByDn(Name dn, Class<T> clazz)

<T> T findOne(LdapQuery query, Class<T> clazz)

<T> List<T> find(LdapQuery query, Class<T> clazz)

<T> List<T> findAll(Class<T> clazz)

<T> List<T> findAll(Name base, SearchControls searchControls, Class<T> clazz)

<T> List<T> findAll(Name base, Filter filter, SearchControls searchControls, Class<T> clazz)

void create(Object entry)
void update(Object entry)
void delete(Object entry)

LdapTemplate实现了该接口,这些方法均可在完成LdapTemplate bean定义后,注入使用。首先按完成基本工程搭建http://www.jianshu.com/p/3aeb49a9befd

ODM注解

需要完成映射的实体类需要使用注释。由org.springframework.ldap.odm.annotations package提供。

  • @Entry - 用于标注实体类(required)
  • @Id - 指明实体DN; 是javax.naming.Name类型(required)
  • @Attribute - 标识实体类需要映射的字段
  • @DnAttribute -
  • @Transient - 标识实体类不需要映射的字段

示例:

@Entry(objectClasses = { "person", "top" }, base="ou=someOu")
public class Person {
   @Id
   private Name dn;

   @Attribute(name="cn")
   @DnAttribute(value="cn", index=1)
   private String fullName;

   // No @Attribute annotation means this will be bound to the LDAP attribute
   // with the same value
   private String description;

   @DnAttribute(value="ou", index=0)
   @Transient
   private String company;

   @Transient
   private String someUnmappedField;
}

@Entry标记,其中的objectClasses定义必须与objectClass完全一致,并且可以指定多个值。在新建和查询object时,ODM会根据此标记进行匹配,无需再指定objectClass。

每个entry必须指定@Id字段,类型为javax.naming.Name,其实就是DN。但是若在LdapContextSource中指定了base,则DN将会按照base截取相对路径。比如,DN为cn=user,ou=users,dc=jayxu,dc=com,base为dc=jayxu,dc=com,则取出的user对象DN为cn=user,ou=users。

对于不需要与LDAP进行映射的字段使用@Transient进行标记

ODM操作

    /**
     * 按部门编号 o 查询部门
     */
    @Test
    public void findOne(){
        LdapQuery ldapQuery = query().where("o").is("039cb846de0e40e08901e85293f642bf");
        LdapDept dept = ldapTemplate.findOne(ldapQuery, LdapDept.class);
        System.out.println(dept);
    }

    /**
     * 按条件过滤部门
     */
    @Test
    public void filter(){
        LdapQueryBuilder ldapQueryBuilder = query();
        Filter filter = new GreaterThanOrEqualsFilter("modifyTimestamp",timestamp);
        ldapQueryBuilder.filter(filter);
        List<LdapDept> depts = ldapTemplate.find(ldapQueryBuilder, LdapDept.class);
        for (LdapDept dept: depts ) {
            System.out.println(dept);
        }
    }

    /**
     * 查询所有部门
     * 类型和base,由LdapDept上的@Entry指定
     */
    @Test
    public void getAllDepts(){
        List<LdapDept> depts = ldapTemplate.findAll(LdapDept.class);
        for (LdapDept dept: depts ) {
            System.out.println(dept);
        }
    }

其中LdapDept是自定义实体类,按照Entry、@Attribute、@Id注解的约定标注好即可。

使用org.springframework.ldap.pool.factory.PoolingContextSource引入连接池

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.pool.factory.PoolingContextSource;
import org.springframework.ldap.pool.validation.DefaultDirContextValidator;
import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy;

import java.util.HashMap;
import java.util.Map;

/**
 * LDAP 的自动配置类
 *
 * 完成连接LDAP 及LdapTemplate 的定义
 */
@ConfigurationProperties(prefix = "ldap")
@PropertySource("classpath:/application.yml")
@Configuration
public class LdapConfiguration {

    private LdapTemplate ldapTemplate;

    @Value("${maxActive}")
    private int maxActive;

    @Value(value = "${maxTotal}")
    private int maxTotal;

    @Value(value = "${maxIdle}")
    private int maxIdle;

    @Value(value = "${minIdle}")
    private int minIdle;

    @Value(value = "${maxWait}")
    private int maxWait;

    @Value(value = "${url}")
    private String LDAP_URL;

    @Value(value = "${base}")
    private String BASE_DC;

    @Value(value = "${dbusername}")
    private String USER_NAME;

    @Value(value = "${password}")
    private String PASS_WORD;

    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        Map<String, Object> config = new HashMap();

        contextSource.setUrl(LDAP_URL);
        contextSource.setBase(BASE_DC);
        contextSource.setUserDn(USER_NAME);
        contextSource.setPassword(PASS_WORD);

        //  解决 乱码 的关键一句
        config.put("java.naming.ldap.attributes.binary", "objectGUID");

        //当需要连接时,池是否一定创建新连接
        contextSource.setPooled(true);
        contextSource.setBaseEnvironmentProperties(config);
        return contextSource;
    }

    /**
     * LDAP pool 配置
     * @return
     */
    @Bean
    public ContextSource poolingLdapContextSource() {
        PoolingContextSource poolingContextSource = new PoolingContextSource();
        poolingContextSource.setDirContextValidator(new DefaultDirContextValidator());
        poolingContextSource.setContextSource(contextSource());
        poolingContextSource.setTestOnBorrow(true);//在从对象池获取对象时是否检测对象有效
        poolingContextSource.setTestWhileIdle(true);//在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性

        poolingContextSource.setMaxActive(maxActive <= 0 ? 20:maxActive);
        poolingContextSource.setMaxTotal(maxTotal <= 0 ? 40:maxTotal);
        poolingContextSource.setMaxIdle(maxIdle <= 0 ? 5:maxIdle);
        poolingContextSource.setMinIdle(minIdle <= 0 ? 5:minIdle);
        poolingContextSource.setMaxWait(maxWait <= 0 ? 5:maxWait);

        TransactionAwareContextSourceProxy proxy = new TransactionAwareContextSourceProxy(poolingContextSource);
        return proxy;
    }

    @Bean
    public LdapTemplate ldapTemplate() {
        if (null == ldapTemplate)
            ldapTemplate = new LdapTemplate(poolingLdapContextSource());
        return ldapTemplate;
    }
}

ODM官方文档
https://docs.spring.io/spring-ldap/docs/2.3.2.RELEASE/reference/#odm

LdapQuery:高级构建查询

LDAP查询生成器参数
该类LdapQueryBuilder及其关联的类旨在支持可以提供给LDAP搜索的所有参数。支持以下参数:
base - 指定搜索应在其中开始的LDAP树中的根DN。
searchScope - 指定搜索应该遍历的LDAP树的深度。
attributes - 指定要从搜索中返回的属性。默认是全部。
countLimit - 指定从搜索返回的最大条目数。
timeLimit - 指定搜索可能需要的最长时间。
Search filter  - 我们正在查找的条目必须符合的条件。
通过LdapQueryBuilder调用query方法创建一个LdapQueryBuilder。它的目的是作为流畅的构建器API,其中首先定义基本参数,然后是过滤器规范调用。一旦过滤器条件已经开始被调用where方法定义LdapQueryBuilder,稍后尝试呼叫例如base将被拒绝。基本搜索参数是可选的,但至少需要一个过滤器规范调用。

使用objectclass person搜索所有条目
 
ldapTemplate.search(
      query().where("objectclass").is("person"),
      new PersonAttributesMapper());

使用objectclass person和cn = John Doe搜索所有条目
 
ldapTemplate.search(
      query().where("objectclass").is("person")
             .and("cn").is("John Doe"),
      new PersonAttributesMapper());

使用在dc = 261consulting,dc = com处开始的objectclass人员搜索所有条目
 
ldapTemplate.search(
      query().base("dc=261consulting,dc=com")
             .where("objectclass").is("person"),
      new PersonAttributesMapper());

搜索具有开始于dc = 261consulting,dc = com的objectclass人员的所有条目,仅返回cn属性
 
ldapTemplate.search(
      query().base("dc=261consulting,dc=com")
             .attributes("cn")
             .where("objectclass").is("person"),
      new PersonAttributesMapper());

//或用标准搜索

ldapTemplate.search(
      query().where("objectclass").is("person"),
             .and(query().where("cn").is("Doe").or("cn").is("Doo));
      new PersonAttributesMapper());

过滤标准
is - 指定等于条件(=)。
gte - 指定一个大于或等于条件(> =)。
lte - 指定小于或等于条件(<=)。
like- 指定查询中可以包含通配符的“like”条件,例如,where("cn").like("J*hn Doe")将导致整个过滤器(cn=J*hn Doe)。
whitespaceWildcardsLike- 指定一个条件,用通配符替换所有的空白,例如where("cn").whitespaceWildcardsLike("John Doe")将导致过滤器。(cn=John*Doe)
isPresent- 指定检查是否存在属性的条件,例如where("cn").isPresent()将导致过滤器(cn=*)。
not- 指定当前条件应否定,例如where("sn").not().is("Doe)将导致过滤器(!(sn=Doe))

硬编码过滤器
有时你会想要指定一个硬编码过滤器作为输入LdapQuery。LdapQueryBuilder有两种方法可用于此目的:
filter(String hardcodedFilter) - 使用指定的字符串作为过滤器。请注意,指定的输入字符串不会以任何方式被触及,这意味着如果您要从用户输入构建过滤器,则此方法不是特别适合。
filter(String filterFormat, String… params)- 使用指定的字符串作为输入MessageFormat,正确编码参数并将其插入到过滤器字符串中的指定位置。

注意:您不能将硬编码过滤器方法与上述where方法混合; 它是一个或另一个。这意味着如果你使用过滤器指定了一个过滤器,filter()如果你尝试where之后再调用,你会得到一个异常。
 

 

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;