Как вы проходите аутентификацию на сервере Active Directory с помощью Spring Security?

Я пишу веб-приложение Spring, которое требует от пользователей входа в систему. В моей компании есть сервер Active Directory, который я хотел бы использовать для этой цели. Однако у меня возникают проблемы с использованием Spring Security для подключения к серверу.

Я использую Spring 2.5.5 и Spring Security 2.0.3 вместе с Java 1.6.

Если я изменю URL-адрес LDAP на неправильный IP-адрес, он не выдаст исключение или что-то еще, поэтому мне интересно, а не пытается ли подключиться к серверу для начала.

Хотя веб-приложение запускается нормально, любая информация, которую я ввожу на странице входа, отклоняется. Ранее я использовал InMemoryDaoImpl, который работал нормально, поэтому остальная часть моего приложения настроена правильно.

Вот мои bean-компоненты, связанные с безопасностью:

  <beans:bean id="ldapAuthProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
    <beans:constructor-arg>
      <beans:bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
        <beans:constructor-arg ref="initialDirContextFactory" />
        <beans:property name="userDnPatterns">
          <beans:list>
            <beans:value>CN={0},OU=SBSUsers,OU=Users,OU=MyBusiness,DC=Acme,DC=com</beans:value>
          </beans:list>
        </beans:property>
      </beans:bean>
    </beans:constructor-arg>
  </beans:bean>

  <beans:bean id="userDetailsService" class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager">
    <beans:constructor-arg ref="initialDirContextFactory" />
  </beans:bean>

  <beans:bean id="initialDirContextFactory" class="org.springframework.security.ldap.DefaultInitialDirContextFactory">
    <beans:constructor-arg value="ldap://192.168.123.456:389/DC=Acme,DC=com" />
  </beans:bean>

person Michael    schedule 17.09.2008    source источник
comment
На самом деле это не столько ответ, сколько уточняющий вопрос - полностью ли вы включили ведение журнала для пакетов безопасности spring?   -  person Jim Kiley    schedule 17.09.2008
comment
У меня ведение журнала включено для всего. Не вижу никаких регистрируемых сообщений... Я обновил свой вопрос с помощью конфигурации Log4J.   -  person Michael    schedule 17.09.2008
comment
Подскажите нужные для этого файлы jar. пожалуйста   -  person Aadi    schedule 06.09.2013


Ответы (8)


У меня было то же самое, что и у вас, и я написал собственный поставщик аутентификации, который выполняет запрос LDAP к серверу Active Directory.

Итак, мои bean-компоненты, связанные с безопасностью:

<beans:bean id="contextSource"
    class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <beans:constructor-arg value="ldap://hostname.queso.com:389/" />
</beans:bean>

<beans:bean id="ldapAuthenticationProvider"
    class="org.queso.ad.service.authentication.LdapAuthenticationProvider">
    <beans:property name="authenticator" ref="ldapAuthenticator" />
    <custom-authentication-provider />
</beans:bean>

<beans:bean id="ldapAuthenticator"
    class="org.queso.ad.service.authentication.LdapAuthenticatorImpl">
    <beans:property name="contextFactory" ref="contextSource" />
    <beans:property name="principalPrefix" value="QUESO\" />
</beans:bean>

Затем класс LdapAuthenticationProvider:

/**
 * Custom Spring Security authentication provider which tries to bind to an LDAP server with
 * the passed-in credentials; of note, when used with the custom {@link LdapAuthenticatorImpl},
 * does <strong>not</strong> require an LDAP username and password for initial binding.
 * 
 * @author Jason
 */
public class LdapAuthenticationProvider implements AuthenticationProvider {

    private LdapAuthenticator authenticator;

    public Authentication authenticate(Authentication auth) throws AuthenticationException {

        // Authenticate, using the passed-in credentials.
        DirContextOperations authAdapter = authenticator.authenticate(auth);

        // Creating an LdapAuthenticationToken (rather than using the existing Authentication
        // object) allows us to add the already-created LDAP context for our app to use later.
        LdapAuthenticationToken ldapAuth = new LdapAuthenticationToken(auth, "ROLE_USER");
        InitialLdapContext ldapContext = (InitialLdapContext) authAdapter
                .getObjectAttribute("ldapContext");
        if (ldapContext != null) {
            ldapAuth.setContext(ldapContext);
        }

        return ldapAuth;
    }

    public boolean supports(Class clazz) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(clazz));
    }

    public LdapAuthenticator getAuthenticator() {
        return authenticator;
    }

    public void setAuthenticator(LdapAuthenticator authenticator) {
        this.authenticator = authenticator;
    }

}

Затем класс LdapAuthenticatorImpl:

/**
 * Custom Spring Security LDAP authenticator which tries to bind to an LDAP server using the
 * passed-in credentials; does <strong>not</strong> require "master" credentials for an
 * initial bind prior to searching for the passed-in username.
 * 
 * @author Jason
 */
public class LdapAuthenticatorImpl implements LdapAuthenticator {

    private DefaultSpringSecurityContextSource contextFactory;
    private String principalPrefix = "";

    public DirContextOperations authenticate(Authentication authentication) {

        // Grab the username and password out of the authentication object.
        String principal = principalPrefix + authentication.getName();
        String password = "";
        if (authentication.getCredentials() != null) {
            password = authentication.getCredentials().toString();
        }

        // If we have a valid username and password, try to authenticate.
        if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
            InitialLdapContext ldapContext = (InitialLdapContext) contextFactory
                    .getReadWriteContext(principal, password);

            // We need to pass the context back out, so that the auth provider can add it to the
            // Authentication object.
            DirContextOperations authAdapter = new DirContextAdapter();
            authAdapter.addAttributeValue("ldapContext", ldapContext);

            return authAdapter;
        } else {
            throw new BadCredentialsException("Blank username and/or password!");
        }
    }

    /**
     * Since the InitialLdapContext that's stored as a property of an LdapAuthenticationToken is
     * transient (because it isn't Serializable), we need some way to recreate the
     * InitialLdapContext if it's null (e.g., if the LdapAuthenticationToken has been serialized
     * and deserialized). This is that mechanism.
     * 
     * @param authenticator
     *          the LdapAuthenticator instance from your application's context
     * @param auth
     *          the LdapAuthenticationToken in which to recreate the InitialLdapContext
     * @return
     */
    static public InitialLdapContext recreateLdapContext(LdapAuthenticator authenticator,
            LdapAuthenticationToken auth) {
        DirContextOperations authAdapter = authenticator.authenticate(auth);
        InitialLdapContext context = (InitialLdapContext) authAdapter
                .getObjectAttribute("ldapContext");
        auth.setContext(context);
        return context;
    }

    public DefaultSpringSecurityContextSource getContextFactory() {
        return contextFactory;
    }

    /**
     * Set the context factory to use for generating a new LDAP context.
     * 
     * @param contextFactory
     */
    public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
        this.contextFactory = contextFactory;
    }

    public String getPrincipalPrefix() {
        return principalPrefix;
    }

    /**
     * Set the string to be prepended to all principal names prior to attempting authentication
     * against the LDAP server.  (For example, if the Active Directory wants the domain-name-plus
     * backslash prepended, use this.)
     * 
     * @param principalPrefix
     */
    public void setPrincipalPrefix(String principalPrefix) {
        if (principalPrefix != null) {
            this.principalPrefix = principalPrefix;
        } else {
            this.principalPrefix = "";
        }
    }

}

И, наконец, класс LdapAuthenticationToken:

/**
 * <p>
 * Authentication token to use when an app needs further access to the LDAP context used to
 * authenticate the user.
 * </p>
 * 
 * <p>
 * When this is the Authentication object stored in the Spring Security context, an application
 * can retrieve the current LDAP context thusly:
 * </p>
 * 
 * <pre>
 * LdapAuthenticationToken ldapAuth = (LdapAuthenticationToken) SecurityContextHolder
 *      .getContext().getAuthentication();
 * InitialLdapContext ldapContext = ldapAuth.getContext();
 * </pre>
 * 
 * @author Jason
 * 
 */
public class LdapAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = -5040340622950665401L;

    private Authentication auth;
    transient private InitialLdapContext context;
    private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

    /**
     * Construct a new LdapAuthenticationToken, using an existing Authentication object and
     * granting all users a default authority.
     * 
     * @param auth
     * @param defaultAuthority
     */
    public LdapAuthenticationToken(Authentication auth, GrantedAuthority defaultAuthority) {
        this.auth = auth;
        if (auth.getAuthorities() != null) {
            this.authorities.addAll(Arrays.asList(auth.getAuthorities()));
        }
        if (defaultAuthority != null) {
            this.authorities.add(defaultAuthority);
        }
        super.setAuthenticated(true);
    }

    /**
     * Construct a new LdapAuthenticationToken, using an existing Authentication object and
     * granting all users a default authority.
     * 
     * @param auth
     * @param defaultAuthority
     */
    public LdapAuthenticationToken(Authentication auth, String defaultAuthority) {
        this(auth, new GrantedAuthorityImpl(defaultAuthority));
    }

    public GrantedAuthority[] getAuthorities() {
        GrantedAuthority[] authoritiesArray = this.authorities.toArray(new GrantedAuthority[0]);
        return authoritiesArray;
    }

    public void addAuthority(GrantedAuthority authority) {
        this.authorities.add(authority);
    }

    public Object getCredentials() {
        return auth.getCredentials();
    }

    public Object getPrincipal() {
        return auth.getPrincipal();
    }

    /**
     * Retrieve the LDAP context attached to this user's authentication object.
     * 
     * @return the LDAP context
     */
    public InitialLdapContext getContext() {
        return context;
    }

    /**
     * Attach an LDAP context to this user's authentication object.
     * 
     * @param context
     *          the LDAP context
     */
    public void setContext(InitialLdapContext context) {
        this.context = context;
    }

}

Вы заметите, что там есть несколько битов, которые вам могут не понадобиться.

Например, моему приложению нужно было сохранить успешно зарегистрированный контекст LDAP для дальнейшего использования пользователем после входа в систему — цель приложения — позволить пользователям войти в систему с помощью своих учетных данных AD, а затем выполнять дополнительные функции, связанные с AD. Поэтому у меня есть собственный токен аутентификации, LdapAuthenticationToken, который я передаю (вместо токена аутентификации Spring по умолчанию), который позволяет мне прикрепить контекст LDAP. В LdapAuthenticationProvider.authenticate() я создаю этот токен и передаю его обратно; в LdapAuthenticatorImpl.authenticate() я присоединяю контекст входа в систему к возвращаемому объекту, чтобы его можно было добавить к объекту аутентификации пользователя Spring.

Кроме того, в LdapAuthenticationProvider.authenticate() я назначаю всем вошедшим в систему пользователям роль ROLE_USER — это то, что позволяет мне затем проверять эту роль в моих элементах intercept-url. Вы захотите, чтобы это соответствовало любой роли, которую вы хотите протестировать, или даже назначать роли на основе групп Active Directory или чего-то еще.

Наконец, как следствие, способ, которым я реализовал LdapAuthenticationProvider.authenticate(), дает всем пользователям с действующими учетными записями AD одну и ту же роль ROLE_USER. Очевидно, что в этом методе вы можете выполнять дальнейшие проверки пользователя (т. е. находится ли пользователь в определенной группе AD?) и таким образом назначать роли или даже проверять какое-либо условие, прежде чем даже предоставить пользователю доступ на all. .

person delfuego    schedule 17.09.2008
comment
Подскажите нужные для этого файлы jar. пожалуйста. - person Aadi; 06.09.2013
comment
Я столкнулся с той же проблемой. Но поскольку многие из этих методов устарели в новых версиях Spring, я больше не могу выполнять ту же работу, что и эта. Я задал свой вопрос здесь stackoverflow. ком/вопросы/32070142/ . Мне будет интересно, если вы можете помочь мне в этом @delfuego - person moha; 19.08.2015

Для справки, в Spring Security 3.1 есть поставщик аутентификации специально для Active Directory.

person Shaun the Sheep    schedule 04.01.2012
comment
+1 Это должен быть лучший ответ. По опыту могу сказать, что вы очень усложняете себе жизнь, если используете LdapAuthenticationProvider для аутентификации в AD. В современных версиях Spring вы можете делать то, что хотите, менее чем за 5 строк кода. См. ссылку, которую Лука предоставил выше. Я также подробно рассказал в своем ответе ниже. - person Cookalino; 17.10.2014

Просто чтобы довести это до современного состояния. Spring Security 3.0 имеет полный пакет с реализациями по умолчанию, посвященными ldap-bind, а также аутентификации запросов и сравнений.

person er4z0r    schedule 03.03.2010

Мне удалось пройти аутентификацию в активном каталоге с помощью Spring Security 2.0.4.

Я задокументировал настройки

http://maniezhilan.blogspot.com/2008/10/spring-security-204-with-active.html/here

person Community    schedule 18.10.2008

Как и в ответе Луки выше:

Spring Security 3.1 имеет поставщика аутентификации специально для Active Directory.

Вот подробное описание того, как это можно легко сделать с помощью ActiveDirectoryLdapAuthenticationProvider.

В resources.groovy:

ldapAuthProvider1(ActiveDirectoryLdapAuthenticationProvider,
        "mydomain.com",
        "ldap://mydomain.com/"
)

В Config.groovy:

grails.plugin.springsecurity.providerNames = ['ldapAuthProvider1']

Это весь код, который вам нужен. Вы можете удалить все остальные настройки grails.plugin.springsecurity.ldap.* в Config.groovy, поскольку они не применяются к этой настройке AD.

Документацию см. по адресу: http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

person Cookalino    schedule 17.10.2014
comment
У меня похожая проблема. Нужно ли мне изменить представление, чтобы защищенный контроллер просто автоматически находил принципала? - person Bmoe; 15.03.2019

Если вы используете Spring security 4, вы также можете реализовать то же самое, используя данный класс

  • SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);

@Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
              .antMatchers("/").permitAll()
              .anyRequest().authenticated();
            .and()
              .formLogin()
            .and()
              .logout();
}

@Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
    ActiveDirectoryLdapAuthenticationProvider authenticationProvider = 
        new ActiveDirectoryLdapAuthenticationProvider("<domain>", "<url>");

    authenticationProvider.setConvertSubErrorCodesToExceptions(true);
    authenticationProvider.setUseAuthenticationRequestCredentials(true);

    return authenticationProvider;
}
}
person Riddhi Gohil    schedule 20.04.2016

Аутентификация LDAP без SSL небезопасна. Любой может увидеть учетные данные пользователя, когда они передаются на сервер LDAP. Я предлагаю использовать протокол LDAPS:\ для аутентификации. Это не требует каких-либо серьезных изменений в весенней части, но вы можете столкнуться с некоторыми проблемами, связанными с сертификатами. Дополнительную информацию см. в разделе Аутентификация LDAP Active Directory в Spring с SSL. подробности

person Gaurav    schedule 16.11.2011

Из ответа Луки выше:

Для справки, в Spring Security 3.1 есть поставщик аутентификации [специально для Active Directory][1].

[1]: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

Я попробовал это с Spring Security 3.1.1: есть некоторые небольшие изменения по сравнению с ldap - группы активного каталога, членом которых является пользователь, проходят как исходный случай.

Ранее в ldap группы писались с заглавной буквы и имели префикс «ROLE_», что упрощало их поиск с помощью текстового поиска в проекте, но, очевидно, могло вызвать проблемы в группе unix, если по какой-то странной причине имелись 2 отдельные группы, различающиеся только регистром ( т.е. учетные записи и учетные записи).

Кроме того, синтаксис требует ручного указания имени и порта контроллера домена, что делает его немного пугающим для избыточности. Конечно, есть способ найти запись DNS SRV для домена в java, т.е. эквивалент (из руководства по Samba 4):

$ host -t SRV _ldap._tcp.samdom.example.com.
_ldap._tcp.samdom.example.com has SRV record 0 100 389 samba.samdom.example.com.

с последующим обычным поиском A:

$ host -t A samba.samdom.example.com.
samba.samdom.example.com has address 10.0.0.1

(На самом деле может потребоваться также поиск записи _kerberos SRV...)

Вышеупомянутое было с Samba4.0rc1, мы постепенно обновляем среду Samba 3.x LDAP до среды Samba AD.

person rjc730    schedule 17.09.2012