Добавление нескольких источников ldap в spring-security в многодоменной среде

Я пытаюсь добавить вторичный ldap contextSource для безопасности spring в среде с разделенным доменом, и, похоже, мне не хватает. Я понимаю, что подобные вопросы задавались и раньше, но это для двух отдельных доменов, регистрирующихся в одном и том же приложении.

Мой первый шаг состоял в том, чтобы добавить вторичный источник контекста в мой 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>

Кроме того, я добавил аргумент-конструктор в 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. Затем я переопределил метод аутентификации, чтобы проверить, содержит ли имя пользователя вторичное 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. Спасибо!


person Dan    schedule 25.11.2013    source источник


Ответы (2)


Из вашего вопроса не совсем понятно, что на самом деле идет не так - например, конфигурация, которую вы имеете, на самом деле не будет загружаться, поскольку она использует Spring Security BindAuthenticator и пытается передать ей два аргумента ContextSource.

На вашем месте я бы не пытался взломать классы внутренней реализации, а вместо этого оставил бы их в покое и написал отдельный класс делегирования на основе ваших критериев выбора.

Сначала я бы определил два отдельных компонента LdapAuthenticationProvider, по одному для каждого домена, и сначала убедился, что вы можете аутентифицировать пользователей с каждым из них, вызывая их непосредственно в модульном тесте. Убедитесь, что вы можете правильно настроить каждый из них для соответствующих доменов, прежде чем пытаться использовать оба вместе.

После этого я подключал их к отдельному делегирующему 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.

person Shaun the Sheep    schedule 25.11.2013
comment
Извините за расплывчатый вопрос, но я переработал свое приложение так, как вы предложили, и теперь оно, похоже, принимает пользователей для двух доменов. Спасибо за вашу помощь. - person Dan; 25.11.2013
comment
@Dan Можете ли вы показать конфигурацию xml безопасности spring? Я пытаюсь разработать аналогичное решение, но с использованием Active Directory. - person Andrzej Purtak; 19.10.2016

Я использовал приведенную ниже реализацию для того же самого, в этом случае, если учетные данные не найдены в одном 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;
    }
}
person Ankit Adlakha    schedule 03.09.2017