You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring LDAP查询Active Directory无法返回用户记录的问题排查

Spring LDAP查询Active Directory报错32(NO_OBJECT)问题排查

我正在编写Spring LDAP程序,用于查询Active Directory中的用户记录,但无法返回结果。相同的查询在Apache Directory Studio中能正常执行并返回我的用户ID,但Spring LDAP运行时抛出以下错误:

org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-0310028C, problem 2001 (NO_OBJECT), data 0, best match of:
'CN=Users,DC=myserver,DC=myschool,DC=edu'
]

理论上应该能找到用户记录,想知道哪里操作有误,以及Spring LDAP无法正常工作的原因。


技术细节

项目是通过Spring Initializr创建的Groovy项目,要求Java 17环境。

Gradle配置

build.gradle相关内容:

plugins {
  id 'groovy'
  id 'org.springframework.boot' version '3.3.1'
  id 'io.spring.dependency-management' version '1.1.5'
}

group = 'edu.myschool'
version = '0.0.1-SNAPSHOT'

java {
  toolchain {
    languageVersion = JavaLanguageVersion.of(17)
  }
}

repositories {
  mavenCentral()
}
	ext {
  set('springShellVersion', "3.3.1")
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
  implementation 'org.apache.groovy:groovy'
  implementation 'org.springframework.boot:spring-boot-starter'
  implementation 'org.springframework.shell:spring-shell-starter'

}

dependencyManagement {
  imports {
    mavenBom "org.springframework.shell:spring-shell-dependencies:${springShellVersion}"
  }
}

Java测试类

为了排除Groovy调用差异,编写了Java测试类(位于Groovy源码目录),核心逻辑在queryForUser方法:

package edu.sunyjcc.testbed;


import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.io.StringWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import javax.naming.directory.SearchControls;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.AttributesMapper;
import javax.naming.directory.Attributes;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.PropertySource;

/** 
 *  This is a minimal example that should demonstrate my problem.
 */
@Component
@PropertySource("classpath:application.properties")
public class HelpTicketExample {

    protected int scope = SearchControls.SUBTREE_SCOPE;
    protected boolean ignorePartialResultException = true;

    @Value("${server.urls}")
    String url;

    @Value("${manager.userdn}")
    String userDn;

    @Value("${manager.password}")
    String password;

    @Value("${username}")
    protected String usernameAttr;

    @Value("${password}")
    protected String passwordAttr;

    // protected LdapTemplate ldapTemplate;
    // protected LdapContextSource ldapContextSource;
    @Value("${basedn}")
    protected String searchBase;

    /** Work around the lack of a println in StringWriter */
    interface Printer {
        String println(String str) throws IOException;
    }

    String filter = "(&(objectClass=person)(sAMAccountName=myusername))";

    /** Print out the configuration information */
    public void showConfig(Printer p) throws IOException {
        p.println("**********************************************************************");
        p.println("* usernameAttr: " + usernameAttr);
        p.println("* searchBase:   " + searchBase);
        p.println("* filter:       " + filter);
        p.println("**********************************************************************");

    }

    public Object queryForUser(String username) throws IOException {
        StringWriter   s = new StringWriter();
        BufferedWriter b = new BufferedWriter(s);
        Printer p = (text) -> {
            b.write(text);
            b.newLine();
            return text;
        };
        //////////////////////////////////////////////////////////////////////
        // Get to work
        //////////////////////////////////////////////////////////////////////
        p.println("************************************************************" );
        p.println("queryForUser(" + username + ")");
        b.newLine();
        showConfig(p);
        //////////////////////////////////////////////////////////////////////
        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl(url);
        contextSource.setBase(searchBase);
        contextSource.setUserDn(userDn);
        contextSource.setPassword(password);
        contextSource.afterPropertiesSet();
        //////////////////////////////////////////////////////////////////////
        AttributesMapper<String> mapper = new AttributesMapper() {
                public String mapFromAttributes(Attributes attributes) {
                    return "Attributes found";
                }
            };
        //////////////////////////////////////////////////////////////////////
        LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
        ldapTemplate.setIgnorePartialResultException(true);
        try {
            ldapTemplate.afterPropertiesSet();
            @SuppressWarnings("unchecked")
                List<String> results = ldapTemplate.search(searchBase,
                                                           filter,
                                                           scope,
                                                           mapper);
        } catch (Exception e) {
            p.println(e.toString());
        }
        //////////////////////////////////////////////////////////////////////
        b.close();
        return s.toString();
    }

    public HelpTicketExample() {
    }
}

程序输出

**********************************************************
queryForUser(myusername)
*********************************************************************
# usernameAttr: sAMAccountName
# searchBase:   cn=Users,dc=myserver,dc=myschool,dc=edu
# filter:       (&(objectClass=person)(sAMAccountName=myusername))
*********************************************************************
org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-0310028C, problem 2001 (NO_OBJECT), data 0, best match of:
        'CN=Users,DC=myserver,DC=myschool,DC=edu'
 ]

问题排查与解决

核心问题:重复设置搜索基准(Search Base)

代码中存在一个关键错误:同时给LdapContextSource设置了searchBase,又在ldapTemplate.search()方法中再次传入相同的searchBase参数。

当你调用LdapContextSource.setBase(searchBase)时,Spring LDAP会自动将这个基准DN作为所有LDAP操作的根路径。此时如果在search()方法中再次传入同一个searchBase,相当于要求LDAP服务器在CN=Users,DC=myserver,DC=myschool,DC=edu下面查找另一个完全相同的DN,这显然不存在,因此抛出NO_OBJECT(错误码32)异常。

修复方案

有两种可选修复方式:

  1. 移除LdapContextSource.setBase(searchBase),仅在search()方法中传入searchBase参数。
  2. 保留LdapContextSource.setBase(searchBase),在search()方法中传入空字符串""或null作为搜索基准,此时LDAP会使用ContextSource中设置的路径作为根。

修改后的queryForUser方法关键代码示例(方案2):

// 保留ContextSource的base设置
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(url);
contextSource.setBase(searchBase); // 保留这行
contextSource.setUserDn(userDn);
contextSource.setPassword(password);
contextSource.afterPropertiesSet();

// 搜索时传入空字符串作为基准
List<String> results = ldapTemplate.search("",
                                           filter,
                                           scope,
                                           mapper);

其他可能的检查点

  • 确认manager.userdn格式正确:Active Directory中绑定用户的DN通常为CN=管理员名称,CN=Users,DC=myserver,DC=myschool,DC=edu,检查是否存在拼写错误。
  • 验证LDAP连接URL:确保包含正确端口(默认389,SSL用636),例如ldap://myserver.myschool.edu:389。
  • 确认用户sAMAccountName准确:虽然Active Directory默认大小写不敏感,但部分环境可能存在差异,需确保与实际值一致。

内容的提问来源于stack exchange,提问作者Big Ed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 08:43:14