1. 概述

LDAP 目录服务器是针对读取优化的层次化数据存储。通常用于存储用户认证和授权所需的用户信息。

本文将探讨 Spring LDAP 的核心 API,包括:

  • 用户认证
  • 用户搜索
  • 用户创建与修改

这些 API 同样适用于管理 LDAP 中的其他类型条目。

2. Maven 依赖

首先添加核心依赖:

<dependency>
    <groupId>org.springframework.ldap</groupId>
    <artifactId>spring-ldap-core</artifactId>
    <version>3.1.2</version>
</dependency>

最新版本可在 spring-ldap-core 查询。

3. 数据准备

为演示功能,先创建基础 LDAP 条目:

ou=users,dc=example,dc=com (objectClass=organizationalUnit)

后续所有操作(创建/修改/认证/搜索)都将在此节点下进行。

4. Spring LDAP API

4.1. ContextSource & LdapTemplate 配置

ContextSource 用于创建 LdapTemplate,认证时需要用到:

@Bean
public LdapContextSource contextSource() {
    LdapContextSource contextSource = new LdapContextSource();
    
    contextSource.setUrl(env.getRequiredProperty("ldap.urls"));
    contextSource.setBase(
      env.getRequiredProperty("ldap.partitionSuffix"));
    contextSource.setUserDn(
      env.getRequiredProperty("ldap.principal"));
    contextSource.setPassword(
      env.getRequiredProperty("ldap.password"));
    
    return contextSource;
}

LdapTemplate 用于条目的增删改查:

@Bean
public LdapTemplate ldapTemplate() {
    return new LdapTemplate(contextSource());
}

4.2. Spring Boot 集成

在 Spring Boot 项目中,使用 Spring Boot Starter Data Ldap 可自动配置 LdapContextSourceLdapTemplate

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>

application.properties 配置连接参数:

spring.ldap.urls=ldap://localhost:18889
spring.ldap.base=dc=example,dc=com
spring.ldap.username=uid=admin,ou=system
spring.ldap.password=secret

直接注入自动配置的 LdapTemplate

@Autowired
private LdapTemplate ldapTemplate;

4.3. 用户认证

实现用户认证的核心逻辑:

public void authenticate(String username, String password) {
    contextSource
      .getContext(
        "cn=" + 
         username + 
         ",ou=users," + 
         env.getRequiredProperty("ldap.partitionSuffix"), password);
}

⚠️ 直接使用 getContext() 是最简单粗暴的认证方式,成功则无异常,失败抛出 AuthenticationException

4.4. 用户创建

创建新用户并存储密码的 SHA 哈希值:

public void create(String username, String password) {
    Name dn = LdapNameBuilder
      .newInstance()
      .add("ou", "users")
      .add("cn", username)
      .build();
    DirContextAdapter context = new DirContextAdapter(dn);

    context.setAttributeValues(
      "objectclass", 
      new String[] 
        { "top", 
          "person", 
          "organizationalPerson", 
          "inetOrgPerson" });
    context.setAttributeValue("cn", username);
    context.setAttributeValue("sn", username);
    context.setAttributeValue
      ("userPassword", digestSHA(password));

    ldapTemplate.bind(context);
}

✅ 关键点:

  • digestSHA() 是自定义方法,生成密码的 SHA 哈希 Base64 字符串
  • 使用 bind() 方法创建条目
  • 必须设置完整的 objectclass 属性链

4.5. 用户修改

修改现有用户信息:

public void modify(String username, String password) {
    Name dn = LdapNameBuilder.newInstance()
      .add("ou", "users")
      .add("cn", username)
      .build();
    DirContextOperations context 
      = ldapTemplate.lookupContext(dn);

    context.setAttributeValues
      ("objectclass", 
          new String[] 
            { "top", 
              "person", 
              "organizationalPerson", 
              "inetOrgPerson" });
    context.setAttributeValue("cn", username);
    context.setAttributeValue("sn", username);
    context.setAttributeValue("userPassword", 
      digestSHA(password));

    ldapTemplate.modifyAttributes(context);
}

❗ 修改流程:

  1. 通过 lookupContext() 获取现有条目
  2. 更新属性值
  3. 调用 modifyAttributes() 提交修改

4.6. 用户搜索

使用过滤器搜索用户:

public List<String> search(String username) {
    return ldapTemplate
      .search(
        "ou=users", 
        "cn=" + username, 
        (AttributesMapper<String>) attrs -> (String) attrs.get("cn").get());
}

🔍 搜索机制:

  • AttributesMapper 从结果条目中提取指定属性
  • Spring 自动对所有匹配条目执行映射,生成属性值列表
  • 支持标准 LDAP 过滤器语法(如 (cn=*)

5. 测试

使用 spring-ldap-test 提供的嵌入式 ApacheDS 服务器进行测试:

@Bean
public TestContextSourceFactoryBean testContextSource() {
    TestContextSourceFactoryBean contextSource 
      = new TestContextSourceFactoryBean();
    
    contextSource.setDefaultPartitionName(
      env.getRequiredProperty("ldap.partition"));
    contextSource.setDefaultPartitionSuffix(
      env.getRequiredProperty("ldap.partitionSuffix"));
    contextSource.setPrincipal(
      env.getRequiredProperty("ldap.principal"));
    contextSource.setPassword(
      env.getRequiredProperty("ldap.password"));
    contextSource.setLdifFile(
      resourceLoader.getResource(
        env.getRequiredProperty("ldap.ldiffile")));
    contextSource.setPort(
      Integer.valueOf(
        env.getRequiredProperty("ldap.port")));
    return contextSource;
}

JUnit 测试示例:

@Test
public void 
  givenLdapClient_whenCorrectSearchFilter_thenEntriesReturned() {
    List<String> users = ldapClient
      .search(SEARCH_STRING);
 
    assertThat(users, Matchers.containsInAnyOrder(USER2, USER3));
}

6. 总结

本文介绍了 Spring LDAP 的核心 API,实现了:

  • 用户认证
  • 用户搜索
  • 用户创建与修改

完整源码见 GitHub 项目。测试位于 Maven 的 "live" profile 下,可通过 -P live 参数运行。

💡 实战建议:

  • 生产环境务必使用 LDAPS 加密连接
  • 密码哈希算法建议升级到更安全的 PBKDF2/BCrypt
  • 复杂查询可考虑使用 Spring Data LDAP 的注解方式

« 上一篇: Java 蚁群优化算法