如何在 spring 安全中使用绑定身份验证机制从任何 ldap 服务器(嵌入式服务器除外)验证 ldap 用户

How to authenticate a ldap user from any ldap server (except embedded server) using bind authentication mechanism in spring security

我想在我公司现有的 spring 引导项目中实施 ldap 身份验证。因此,为此,我首先尝试实施一个示例 spring 启动应用程序,在其中我将从我公司的 ldap 服务器验证 ldap 用户。如果它有效,那么我将在我现有的项目中实现相同的代码,我只想使用 ldap 绑定身份验证机制来实现这一点。

以下是我为示例应用程序编写的 spring 安全性中使用绑定身份验证机制对 ldap 用户进行身份验证的代码:

Here is the server structure for user, from this image you will get idea what are attribute available for an user

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>SampleApp2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>SampleApp2</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
                <dependency>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-ldap</artifactId>
                        <version>5.5.3</version>
                        <type>jar</type>
                </dependency>
     <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <type>jar</type>
     </dependency>
         <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

HomeController.java

package com.example.SampleApp2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

  @GetMapping("/")
  public String index() {
    return "Welcome to the home page!";
  }

}

WebSecurityConfig.java

package com.example.SampleApp2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public XyzEncryption xyzEncrption() { // custom password encoder which is used in company's ldap server to authenticate user, even though I didn't use it any where
    
        return new XyzEncryption();
    }
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        .anyRequest().fullyAuthenticated()
        .and()
      .formLogin();
  }
  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
    .ldapAuthentication()     
    .contextSource()
    .url("ldap://in.xyz.com:389/DC=in,DC=xyz,DC=com")
    .managerDn("CN=Rohit Sarkar,OU=Engg,OU=KSPL Users,DC=in,DC=xyz,DC=com") //User Dn by which I am binding the connection with server
    .managerPassword("PasswordOfRohitSarkar")
    .and()
    .userSearchFilter("sAMAccountName=abdulg")
    .userDnPatterns("CN=Abdul Gaffar,OU=Engg,OU=KSPL Users,DC=in,DC=xyz,DC=com"); // User dn which need to be authenticated from server      
  }
}

I am getting this kind of default login page from spring security and loging with abdulg and it's password

但是出现错误:

org.springframework.ldap.PartialResultException: Unprocessed Continuation Reference(s); nested exception is javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name ''
    at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:216) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:385) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:328) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:629) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:570) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForMultipleAttributeValues(SpringSecurityLdapTemplate.java:197) ~[spring-security-ldap-5.5.3.jar:5.5.3]
    at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGroupMembershipRoles(DefaultLdapAuthoritiesPopulator.java:223) ~[spring-security-ldap-5.5.3.jar:5.5.3]
    at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGrantedAuthorities(DefaultLdapAuthoritiesPopulator.java:203) ~[spring-security-ldap-5.5.3.jar:5.5.3]
    at org.springframework.security.ldap.authentication.LdapAuthenticationProvider.loadUserAuthorities(LdapAuthenticationProvider.java:197) ~[spring-security-ldap-5.5.3.jar:5.5.3]
    at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:83) ~[spring-security-ldap-5.5.3.jar:5.5.3]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.5.3.jar:5.5.3]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201) ~[spring-security-core-5.5.3.jar:5.5.3]
    at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:85) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:222) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:132) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
    at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:126) [spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.boot.web.servlet.support.ErrorPageFilter.access[=14=]0(ErrorPageFilter.java:64) [spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilterInternal(ErrorPageFilter.java:101) [spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
    at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:119) [spring-boot-2.5.6.jar:2.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:196) [catalina.jar:8.5.70]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [catalina.jar:8.5.70]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:544) [catalina.jar:8.5.70]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [catalina.jar:8.5.70]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [catalina.jar:8.5.70]
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698) [catalina.jar:8.5.70]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [catalina.jar:8.5.70]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:364) [catalina.jar:8.5.70]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:624) [tomcat-coyote.jar:8.5.70]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-coyote.jar:8.5.70]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831) [tomcat-coyote.jar:8.5.70]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1650) [tomcat-coyote.jar:8.5.70]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-coyote.jar:8.5.70]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-util.jar:8.5.70]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-util.jar:8.5.70]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-util.jar:8.5.70]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_302]
Caused by: javax.naming.PartialResultException: Unprocessed Continuation Reference(s)
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3024) ~[na:1.8.0_302]
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2998) ~[na:1.8.0_302]
    at com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1874) ~[na:1.8.0_302]
    at com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1797) ~[na:1.8.0_302]
    at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:392) ~[na:1.8.0_302]
    at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:358) ~[na:1.8.0_302]
    at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:341) ~[na:1.8.0_302]
    at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:267) ~[na:1.8.0_302]
    at org.springframework.ldap.core.LdapTemplate.executeSearch(LdapTemplate.java:322) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:363) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    ... 72 common frames omitted

我使用了以下代码而不是硬编码:

.userSearchFilter("sAMAccountName={0}")

仍然出现同样的错误。

我已尝试使用广告身份验证提供程序来验证用户并成功验证,但由于它不是 spring 安全性的标准方法,我想使用绑定身份验证而不想使用密码比较鉴权。我想隐藏用户的密码。

以下是使用广告身份验证提供商对用户进行身份验证的代码:

package com.example.SampleApp2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public KovairEncryption kovairEncrption() {
    
        return new KovairEncryption();
    }
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        .anyRequest().fullyAuthenticated()
        .and()
      .formLogin();
  }

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
  
    auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
  }
  
    @Bean
    public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
        ActiveDirectoryLdapAuthenticationProvider authenticationProvider =
        new ActiveDirectoryLdapAuthenticationProvider("in.xyz.com", "ldap://in.xyz.com")
        authenticationProvider.setConvertSubErrorCodesToExceptions(true);
        authenticationProvider.setUseAuthenticationRequestCredentials(true);
        return authenticationProvider;
    }
}

我也尝试过传统的 java 代码来验证广告用户它也工作正常但无法使用绑定身份验证机制进行身份验证。我已经阅读了很多文章并浏览了很多 spring 参考文档,但找不到任何解决方案。我已经花了将近一周的时间才找到合适的解决方案。

好的。所以在花了很多时间之后我得到了解决方案。

首先上面提到的代码片段没有错,这个错误的原因是-

Accroding to spring security doc under section 18.4.4,在活动目录搜索的情况下,在成功验证用户后,LdapAuthenticationProvider 将尝试通过调用配置的 LdapAuthoritiesPopulator 为用户加载一组权限豆。 DefaultLdapAuthoritiesPopulator 是一个实现,它将通过在目录中搜索用户所属的组(通常是目录中的 groupOfNames 或 groupOfUniqueNames 条目)来加载权限。

如果您只想使用 LDAP 进行身份验证,但要从不同的来源(例如数据库)加载权限,那么您可以提供您自己的此接口实现并注入它。

但我忽略了在成功验证用户后返回的权限,这就是我收到此错误的原因。

解决问题的方法很少:

第一种方式: 使用 Context.REFERRALfollow

缺点:跟随推荐和验证用户需要花费很多时间

第二种方式:通过实施LdapAuthoritiesPopulator

CustomAuthoritiesPopulator.java

@Component
public class CustomAuthoritiesPopulator implements LdapAuthoritiesPopulator {

    @Override
    public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations dco, String string) {
        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
        grantedAuthorities.add(new SimpleGrantedAuthority("ADMIN"));
        return grantedAuthorities;
    }
    
}

WebSecurityConfig.java

@Autowired
CustomAuthoritiesPopulator authoritiespopulator;
// I have mentioned only configure() method here, others code will be as it is

@Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
    .ldapAuthentication()     
    .contextSource()
    .url("ServerUrl")
    .managerDn("BindUserDN") 
    .managerPassword("BindUserPassword")
    .and()
    .ldapAuthoritiesPopulator(authoritiespopulator)
    .userSearchFilter("sAMAccountName=UserName")
    
  }

注意:如果您想设置一些自定义权限,这将很有帮助

第三种方式: 使用 .setIgnorePartialResultException(true)

WebSecurityConfig.java

@Bean
LdapContextSource ldapContextSource() {
   LdapContextSource ldapContextSource = new LdapContextSource();
   ldapContextSource.setUrl("ServerUrl");
   ldapContextSource.setUserDn("BindUserDN");
   dapContextSource.setPassword("BindUserPassword");
   ldapContextSource.afterPropertiesSet();
}

@Bean
public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() throws Exception { 
DefaultLdapAuthoritiesPopulator authoritiespopulator= new DefaultLdapAuthoritiesPopulator(ldapContextSource(), "base");
        authoritiespopulator.setIgnorePartialResultException(true);
        return authoritiespopulator;
    }

@Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
    .ldapAuthentication()     
    .contextSource()
    .url("ServerUrl")
    .managerDn("BindUserDN") 
    .managerPassword("BindUserPassword")
    .and()
    .ldapAuthoritiesPopulator(authoritiespopulator)
    .userSearchFilter("sAMAccountName=UserName")
    
  }