在多域环境中向spring-security添加多个ldap源

时间:2013-11-25 15:20:41

标签: java spring spring-security ldap ldap-client

我正在尝试在分割域环境中添加辅助ldap contextSource以弹出安全性,而且我似乎很快就会出现问题。我之前已经意识到类似的问题,但这是针对两个单独的域登录到同一个应用程序。

我的第一步是将辅助上下文源添加到我的security-config xml文件中,如下所示:

<beans:bean id="secondaryContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <beans:constructor-arg value="ldap://<ldap address>:389/DC=example,DC=com"/>
    <beans:property name="userDn" value="CN=BindAccount,CN=Users,DC=example,DC=com" />
    <beans:property name="password" value="examplepw" />
</beans:bean>

此外,我将构造函数-arg添加到ldapAuthenticationProvider并将BindAuthenticator替换为我的自定义类,如下所示:

    <beans:bean id="ldapAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">    
    <beans:constructor-arg>
        <beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
            <beans:constructor-arg ref="contextSource" />
            <beans:constructor-arg ref="secondaryContextSource" />
            <beans:property name="userSearch">
                <beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
                  <beans:constructor-arg index="0" value="CN=Users"/>
                  <beans:constructor-arg index="1" value="(userPrincipalName={0})"/>
                  <beans:constructor-arg index="2" ref="contextSource" />
                </beans:bean>
            </beans:property>

        </beans:bean>
    </beans:constructor-arg>       
     <beans:property name="userDetailsContextMapper">
        <beans:bean id="employeeServiceFacade" class="com.example.service.security.EmployeeServiceFacade" />
    </beans:property>   
      <beans:constructor-arg>
        <beans:bean class="com.example.web.security.CustomLdapAuthoritiesPopulator" />
    </beans:constructor-arg>
</beans:bean>

然后我尝试扩展BindAuthenticator以接受并在构造函数中设置辅助上下文源。最初我无法使用它,所以我完全重写了BindAuthenticator类并扩展了AbstractLdapAuthenticator来绕过BindAuthenticator。然后我覆盖authenticate方法以检查用户名是否包含辅助DN,如果是,我会再次调用bindWithDn尝试重新绑定到辅助域。这就是我认为这一切都错了,因为当它试图获得新的Dn时它失败了。基本上它声明它无法绑定到域。 (我已经三次检查了域设置并使用ldap管理控制台连接到它并为我的应用程序采取了这些设置)这是我的扩展BindAuthenticator

public class ExtendedBindAuthenticator extends AbstractLdapAuthenticator {

    private BaseLdapPathContextSource secondaryContextSource;

    public void setSecondContextSource(BaseLdapPathContextSource secondContextSource) {
        this.secondaryContextSource = secondaryContextSource;
    }


    public ExtendedBindAuthenticator(BaseLdapPathContextSource contextSource, BaseLdapPathContextSource secondContextSource) {
        super(contextSource);
        this.secondaryContextSource = secondaryContextSource;
    }


    public DirContextOperations authenticate(Authentication authentication) {
        DirContextOperations user = null;
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                "Can only process UsernamePasswordAuthenticationToken objects");

        String username = authentication.getName();
        String password = (String)authentication.getCredentials();         

        if(username.contains("secondDomain")) {
            DirContextOperations secondaryUser = getUserSearch().searchForUser(username);
            this.bindWithDn(secondaryUser.getDn().toString(), username, password);

        }


        if (!StringUtils.hasLength(password)) {

            throw new BadCredentialsException(messages.getMessage("BindAuthenticator.emptyPassword",
                    "Empty Password"));
        }

        // If DN patterns are configured, try authenticating with them directly
        for (String dn : getUserDns(username)) {
            user = this.bindWithDn(dn, username, password);

            if (user != null) {
                break;
            }
        }

        // Otherwise use the configured search object to find the user and authenticate with the returned DN.
        if (user == null && getUserSearch() != null) {
            DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
            user = bindWithDn(userFromSearch.getDn().toString(), username, password);
        }

        if (user == null) {
            throw new BadCredentialsException(
                    messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
        }

        return user;
    }

    private DirContextOperations bindWithDn(String userDnStr, String username, String password) {
        BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();

        if(username.contains("secondDomain")) {
            ctxSource = secondaryContextSource;
        }

        DistinguishedName userDn = new DistinguishedName(userDnStr);
        DistinguishedName fullDn = new DistinguishedName(userDn);
        fullDn.prepend(ctxSource.getBaseLdapPath());


        DirContext ctx = null;
        try {
            ctx = getContextSource().getContext(fullDn.toString(), password);
            // Check for password policy control
            PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);



            Attributes attrs = ctx.getAttributes(userDn, getUserAttributes());

            DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());

            if (ppolicy != null) {
                result.setAttributeValue(ppolicy.getID(), ppolicy);
            }

            return result;
        } catch (NamingException e) {
            // This will be thrown if an invalid user name is used and the method may
            // be called multiple times to try different names, so we trap the exception
            // unless a subclass wishes to implement more specialized behaviour.
            if ((e instanceof org.springframework.ldap.AuthenticationException)
                    || (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
                handleBindException(userDnStr, username, e);
            } else {
                throw e;
            }
        } catch (javax.naming.NamingException e) {
            throw LdapUtils.convertLdapException(e);
        } finally {
            LdapUtils.closeContext(ctx);
        }

        return null;
    }

    /**
     * Allows subclasses to inspect the exception thrown by an attempt to bind with a particular DN.
     * The default implementation just reports the failure to the debug logger.
     */
    protected void handleBindException(String userDn, String username, Throwable cause) {
       System.out.println("Failed to bind as " + userDn + ": " + cause);
    }

}

如果有人对这种事情有任何经验,我会非常感激,因为我在这个问题上找不到多少。我希望有人可以告诉我,如果我走在正确的轨道上,或者我是否应该以不同的方式解决这个问题。为了清楚起见我使用的是spring-security-ldap而不是spring-ldap。还要提一下,我的pom文件中包含了所有依赖项。谢谢!

2 个答案:

答案 0 :(得分:2)

从你的问题中不完全清楚实际出现了什么问题 - 例如,你使用的配置实际上并没有加载,因为它使用的是Spring Security的BindAuthenticator并尝试向它传递两个ContextSource参数

如果我是你,我会避免试图破解内部实现类,而是根据您的选择标准单独留下它们并编写一个单独的委托类。

首先,我将定义两个单独的LdapAuthenticationProvider bean,每个域一个,首先确保您可以通过在单元测试中直接调用它们来对每个用户进行身份验证。在尝试同时使用它们之前,请确保您可以为各自的域正确配置它们。

之后我会将它们连接到一个单独的委托AuthenticationProvider。类似的东西:

public class DelegatingLdapAuthenticationProvider implements AuthenticationProvider {
    // Inject these via the app context
    private LdapAuthenticationProvider primary;
    private LdapAuthenticationProvider secondary;

    public Authentication authenticate(Authentication a) {
        if (a.getName().contains("secondDomain")) {
            return secondary.authenticate(a);
        } else {
            return primary.authenticate(a);
        }
    }
}

然后我将此bean配置为Spring Security实际调用的身份验证提供程序。

答案 1 :(得分:0)

我使用了以下相同的实现,如果在一个ldap / ad中找不到凭据,它将检入另一个ldap / ad并做出相应的响应:

@Configuration
@EnableWebSecurity
@EnableConfigurationProperties
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            configureLdapP(auth);
            configureActiveDirectoryP(auth);
            configureLdap(auth);
            configureActiveDirectory(auth);
        }

    @Bean
    protected LdapLoginRequestFilter ldapLoginRequestFilter() throws Exception {
        return new LdapLoginRequestFilter("/login/ldap", authenticationManager(), authenticationResultHandler);
    }


    private void configureActiveDirectory(AuthenticationManagerBuilder auth) {
        ActiveDirectoryLdapAuthenticationProvider adProvider = activeDirectoryLdapAuthenticationProvider();
        if(adProvider != null) auth.authenticationProvider(adProvider);
    }

    private void configureActiveDirectoryP(AuthenticationManagerBuilder auth) {
        ActiveDirectoryLdapAuthenticationProvider adProvider = activeDirectoryLdapAuthenticationProviderP();
        if(adProvider != null) auth.authenticationProvider(adProvider);
    }

    private void configureLdap(AuthenticationManagerBuilder auth) throws Exception {
        String ldapServerUrl = "ldap url 1";
        String ldapUserDnPattern = "ldap user dn pattern";
        if (StringUtils.isNotBlank(ldapServerUrl) && StringUtils.isNotBlank(ldapUserDnPattern)) {
            auth.ldapAuthentication()
            .userDnPatterns(ldapUserDnPattern)
            .contextSource().url(ldapServerUrl);
        }
    }

    private void configureLdapP(AuthenticationManagerBuilder auth) throws Exception {
        String ldapServerUrl = "ldap url 2";
        String ldapUserDnPattern = "ldap user dn pattern";
        if (StringUtils.isNotBlank(ldapServerUrl) && StringUtils.isNotBlank(ldapUserDnPattern)) {
            auth.ldapAuthentication()
            .userDnPatterns(ldapUserDnPattern)
            .contextSource().url(ldapServerUrl);
        }
    }
    @Bean
    protected ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {


        ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("domain 2", "ad url 2",
                null);
        provider.setConvertSubErrorCodesToExceptions(true);
        provider.setUseAuthenticationRequestCredentials(true);
        provider.setUserDetailsContextMapper(new CustomUserDetailsContextMapper());
        return provider;
    }

    @Bean
    protected ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProviderP() {

        ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("domain1", "ad url 1",
                null);
        provider.setConvertSubErrorCodesToExceptions(true);
        provider.setUseAuthenticationRequestCredentials(true);
        provider.setUserDetailsContextMapper(new CustomUserDetailsContextMapper());
        return provider;
    }
}