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

多LDAP服务下LdapAuthenticationProvider单次错密触发账号锁定问题

问题说明

使用spring-security 5.4.2对接LDAP认证时,当配置了多个LDAP服务器地址时,仅输错一次密码就会触发账号锁定,该问题在JDK 8u252版本不会复现,在JDK 8u301及更高版本可以稳定复现。
抓包确认高版本JDK会向所有配置的LDAP服务器都发送密码校验请求,错误次数叠加超过Bad-Pwd-Count阈值就会锁定账号。

复现步骤

  1. 启动测试LDAP服务:
docker pull rroemhild/test-openldap
docker run --rm -p 10389:10389 -p 10636:10636 rroemhild/test-openldap
tcpdump -i docker0 port 10389 or port 10636
  1. 运行测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@EnableWebSecurity
@WebAppConfiguration
@EnableWebMvc
@ContextConfiguration(loader = AnnotationConfigWebContextLoader.class)
public class LdapAuthOpenLdapTest {

    @Configuration
    @EnableWebSecurity
    @WebAppConfiguration
    @EnableWebMvc
    static class LdapSecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) {
            auth.authenticationProvider(ldapAuthProvider());
        }

        @Bean
        DefaultSpringSecurityContextSource ldapServer() {
            DefaultSpringSecurityContextSource result = new DefaultSpringSecurityContextSource(
                    "ldap://localhost:10389 ldaps://localhost:10636");
            result.setUserDn("cn=admin,dc=planetexpress,dc=com");
            result.setPassword("GoodNewsEveryone");
            return result;
        }

        @Bean
        FilterBasedLdapUserSearch userSearch() {
            return new FilterBasedLdapUserSearch("dc=planetexpress,dc=com",
                    "(&(uid={0})(objectClass=inetOrgPerson))", ldapServer());
        }

        @Bean
        LdapAuthenticationProvider ldapAuthProvider() {
            BindAuthenticator bindAuthenticator = new BindAuthenticator(
                    ldapServer());
            bindAuthenticator.setUserSearch(userSearch());
            DefaultLdapAuthoritiesPopulator authoritiesPopulator = new DefaultLdapAuthoritiesPopulator(
                    ldapServer(), "ou=people,dc=planetexpress,dc=com");
            authoritiesPopulator.setGroupRoleAttribute("cn");
            authoritiesPopulator.setRolePrefix("ROLE_");
            authoritiesPopulator.setConvertToUpperCase(true);
            return new LdapAuthenticationProvider(bindAuthenticator,
                    authoritiesPopulator);
        }

    }

    @Autowired
    LdapAuthenticationProvider ldapAuthProvider;

    @Test
    public void test() {
        Exception ex = null;
        try {
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                    "professor", "professor1");
            Authentication auth = ldapAuthProvider.authenticate(token);
            System.out.println(auth);
        } catch (Exception e) {
            e.printStackTrace();
        }
        assertThat(ex, is(nullValue()));
    }
}
问题解答

哪一种行为符合预期?

JDK 8u252的行为才符合设计预期。
该问题是高版本JDK引入的LDAP实现逻辑bug:原本LDAP客户端的重试逻辑设计为,仅当LDAP服务器连接失败、无响应这类网络/服务不可用场景时,才会尝试下一个配置的LDAP节点。密码错误属于业务层面的认证失败,已经明确得到了服务器的拒绝响应,不应该触发重试逻辑。JDK 8u271之后的版本错误将认证失败也纳入了重试触发条件,才会遍历所有配置的LDAP服务器发送认证请求,导致错误次数叠加。

如何避免高版本JDK下的账号锁定问题?

可按优先级选择以下任意方案解决:

  • 方案1(改动最小,无需改代码):在应用启动参数中添加JVM系统属性禁用非必要的LDAP重试:
    -Dcom.sun.jndi.ldap.connect.retry=false
    
    配置后只会在首个LDAP节点连接失败时才尝试下一个节点,密码错误会直接返回,不会触发后续节点的认证请求。
  • 方案2(原生修复):升级JDK到已修复该bug的版本。JDK 8u371及以上的JDK 8更新版本,或者11、17等更高LTS版本的JDK都已经修复了这个逻辑问题,升级后即可原生解决。
  • 方案3(代码层面兼容):修改Spring Security LDAP配置,自行实现LDAP节点选择逻辑,每次认证仅向单个可用的LDAP节点发起请求,认证失败后直接终止流程,不依赖JDK的内置重试逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 17:57:03