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

Spring Boot LDAP认证示例对接ActiveDirectory失败问题排查

Spring Boot LDAP对接本地ActiveDirectory认证问题

问题背景

我找到一个基于嵌入式LDAP服务器的Spring Boot LDAP认证示例,该示例可正常运行,仅需添加一个配置类即可实现应用登录拦截。

修改示例代码中的URL、base dn等配置,并添加本地ActiveDirectory(非Azure AD)的访问userDN和密码后,登录时始终抛出「Bad credentials」错误。调试发现Spring LDAP已成功获取到AD用户数据(如UserDetails包含用户邮箱),但password字段为null,导致密码对比失败,最终在org.springframework.security.authentication.dao.additionalAuthenticationChecks()方法中抛出BadCredentialsException。

疑问

  1. 为何password属性为null?我理解该字段应存储密码哈希,但用ldapsearch查看AD响应未发现类似哈希内容。不过该userDN可在其他应用正常使用,请问如何正确获取密码信息?
  2. 该示例未处理密码哈希:预加载嵌入式LDAP的LDIF文件中userPassword属性为明文,代码中使用NoOpPasswordEncoder。如何修改才能适配ActiveDirectory?

我的配置代码

package com.asbnotebook.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;

@Configuration
public class LdapSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {
        
        var cs = new DefaultSpringSecurityContextSource("ldaps://ad.company.local/dc=company,dc=local");
        cs.setUserDn("cn=robot1,ou=robots");
        cs.setPassword("secret");
        cs.afterPropertiesSet();

        var manager = new LdapUserDetailsManager(cs);
        manager.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=company_user", "cn"));
        manager.setGroupSearchBase("ou=company_groups");

        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

问题解答

1. 为什么password属性为null?

ActiveDirectory不会对外暴露用户的密码哈希,哪怕是拥有读取权限的服务账号也无法获取。这是AD的安全设计——密码哈希是存储在AD内部的敏感数据,不会通过LDAP查询返回。

你之前用嵌入式LDAP能拿到password是因为示例里的LDIF是明文存储且配置了允许读取,但AD完全不同:它的认证逻辑是让AD自己验证密码,而不是把密码哈希取出来在本地对比。

其他应用能用该userDN正常登录,是因为那些应用用的是「绑定认证」模式:先通过服务账号绑定AD,找到用户的完整DN,再用用户输入的密码以该用户DN重新绑定AD,以此验证密码是否正确——而不是在本地做密码比对。

2. 如何修改代码适配ActiveDirectory?

当前配置用的LdapUserDetailsManager是针对通用LDAP的用户管理类,不适合AD的认证逻辑。正确做法是改用Spring Security专门为ActiveDirectory提供的ActiveDirectoryLdapAuthenticationProvider,它会自动处理AD的绑定认证流程:

修改后的配置类如下:

package com.asbnotebook.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;

@Configuration
public class LdapSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
    }

    @Bean
    public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
        ActiveDirectoryLdapAuthenticationProvider provider = 
            new ActiveDirectoryLdapAuthenticationProvider("company.local", "ldaps://ad.company.local/");
        // 允许用户输入用户名时不带域名后缀(比如输入"user1"而不是"user1@company.local")
        provider.setConvertSubErrorCodesToExceptions(true);
        provider.setUseAuthenticationRequestCredentials(true);
        // 设置服务账号(可选,如果AD允许匿名绑定查找用户DN可以不设置,但建议设置)
        provider.setContextSource(userDnContextSource());
        return provider;
    }

    @Bean
    public DefaultSpringSecurityContextSource userDnContextSource() {
        DefaultSpringSecurityContextSource contextSource = 
            new DefaultSpringSecurityContextSource("ldaps://ad.company.local/dc=company,dc=local");
        contextSource.setUserDn("cn=robot1,ou=robots");
        contextSource.setPassword("secret");
        contextSource.afterPropertiesSet();
        return contextSource;
    }
}

关键说明:

  • ActiveDirectoryLdapAuthenticationProvider会自动完成:
    1. 通过服务账号绑定AD,根据用户名查找用户的完整DN
    2. 用用户输入的密码以该用户DN重新绑定AD,由AD验证密码正确性
  • 不需要再配置PasswordEncoder,因为密码验证是交给AD做的,本地不需要处理哈希或明文比对
  • 如果你的AD允许用户用用户名@域名的格式登录,也可以不用设置服务账号,直接让ActiveDirectoryLdapAuthenticationProvider自动处理绑定

另外,注意检查AD的LDAPS端口(默认636)是否开放,服务账号是否有足够权限查找用户信息。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 07:50:28