Comment s'authentifier auprès d'un serveur Active Directory à l'aide de Spring Security?

StackOverflow https://stackoverflow.com/questions/84680

Question

J'écris une application Web Spring qui nécessite que les utilisateurs se connectent. Ma société possède un serveur Active Directory que je souhaiterais utiliser à cette fin. Cependant, je ne parviens pas à utiliser Spring Security pour me connecter au serveur.

J'utilise Spring 2.5.5 et Spring Security 2.0.3, ainsi que Java 1.6.

Si je modifie l'URL LDAP en utilisant la mauvaise adresse IP, cela ne génère aucune exception, je me demande donc si essaye même de se connecter au serveur pour commencer.

Bien que l'application Web démarre correctement, toutes les informations que je saisis dans la page de connexion sont rejetées. J'avais précédemment utilisé InMemoryDaoImpl, qui fonctionnait bien. Le reste de mon application semble donc être configuré correctement.

Voici mes beans liés à la sécurité:

  <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>
Était-ce utile?

La solution

J'ai vécu la même expérience que vous et vous avez fini par écrire un fournisseur d'authentification personnalisé qui effectue une requête LDAP sur le serveur Active Directory.

Mes beans liés à la sécurité sont donc les suivants:

<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>

Ensuite, la classe 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;
    }

}

Ensuite, la classe 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 = "";
        }
    }

}

Et enfin, la classe 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;
    }

}

Vous remarquerez qu'il y a quelques bits dont vous pourriez ne pas avoir besoin.

Par exemple, mon application devait conserver le contexte LDAP connecté avec succès pour pouvoir être utilisée par l'utilisateur une fois connecté. L'objectif de l'application est de permettre aux utilisateurs de se connecter via leurs informations d'identification AD, puis d'effectuer d'autres tâches liées à AD. les fonctions. Donc, à cause de cela, j'ai un jeton d'authentification personnalisé, LdapAuthenticationToken, que je transmets (plutôt que le jeton d'authentification par défaut de Spring) qui me permet d'attacher le contexte LDAP. Dans LdapAuthenticationProvider.authenticate (), je crée ce jeton et le repasse; Dans LdapAuthenticatorImpl.authenticate (), j'attache le contexte connecté à l'objet de retour afin qu'il puisse être ajouté à l'objet d'authentification Spring de l'utilisateur.

De plus, dans LdapAuthenticationProvider.authenticate (), j'attribue le rôle ROLE_USER à tous les utilisateurs connectés. C'est ce qui me permet de tester ce rôle dans mes éléments intercept-url. Faites en sorte que cela corresponde au rôle que vous souhaitez tester ou même que vous affectiez des rôles en fonction de groupes Active Directory ou autres.

Enfin, et corollairement à cela, la façon dont j'ai implémenté LdapAuthenticationProvider.authenticate () attribue le même rôle ROLE_USER à tous les utilisateurs possédant un compte AD valide. Évidemment, avec cette méthode, vous pouvez effectuer des tests supplémentaires sur l'utilisateur (l'utilisateur est-il dans un groupe AD spécifique?) Et attribuer des rôles de cette manière, ou même tester une condition avant même d'accorder à l'utilisateur un accès à tous .

Autres conseils

Pour référence, Spring Security 3.1 dispose d’un fournisseur d’authentification spécifiquement pour Active Directory .

Juste pour mettre cela à jour. Spring Security 3.0 a un package complet avec les implémentations par défaut consacrées à ldap-bind ainsi qu’à l’authentification des requêtes et des comparaisons.

J'ai pu m'authentifier auprès d'Active Directory à l'aide de Spring Security 2.0.4.

J'ai documenté les paramètres

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

Comme dans la réponse de Luc ci-dessus:

  

Spring Security 3.1 dispose d'un fournisseur d'authentification spécifique à Active Directory.

Voici comment cela peut être facilement réalisé avec ActiveDirectoryLdapAuthenticationProvider.

Dans resources.groovy:

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

Dans Config.groovy:

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

C'est tout le code dont vous avez besoin. Vous pouvez très bien supprimer tous les autres paramètres grails.plugin.springsecurity.ldap. * De Config.groovy car ils ne s’appliquent pas à cette configuration AD.

Pour la documentation, voir: http: / /docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

L'authentification LDAP sans SSL n'est pas sécurisée. Toute personne peut voir les informations d'identification de l'utilisateur lorsque celles-ci sont transférées sur le serveur LDAP. Je suggère d'utiliser LDAPS: \ protocole pour l'authentification. Cela ne nécessite aucun changement majeur sur la partie ressort, mais vous pouvez rencontrer quelques problèmes liés aux certificats. Voir l'authentification LDAP Active Directory dans Spring avec SSL pour plus d'informations. détails

De la réponse de Luc ci-dessus:

  

Pour référence, Spring Security 3.1 a un fournisseur d'authentification   [spécifiquement pour Active Directory] [1].

     

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

J'ai essayé ce qui précède avec Spring Security 3.1.1: il y a de légers changements par rapport à ldap - les répertoires Active Directory dont l'utilisateur est membre appartiennent à la casse d'origine.

Précédemment sous ldap, les groupes étaient capitalisés et précédés de "ROLE_", ce qui les rendait faciles à trouver avec une recherche de texte dans un projet, mais pouvait évidemment poser problème dans un groupe unix si différenciés par cas (comptes et comptes).

De plus, la syntaxe nécessite la spécification manuelle du nom du contrôleur de domaine et du port, ce qui rend la redondance un peu effrayante. Il existe sûrement un moyen de rechercher l’enregistrement DNS SRV du domaine en java, c’est-à-dire l’équivalent de (extrait de Samba 4 howto):

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

suivi d'une recherche régulière:

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

(En fait, vous devrez peut-être aussi consulter l'enregistrement SRV _kerberos ...)

Ce qui précède était avec Samba4.0rc1, nous mettons progressivement à niveau l’environnement LDAP Samba 3.x vers Samba AD one.

  

Si vous utilisez Spring Security 4 , vous pouvez également le mettre en œuvre à l'aide de   classe donnée

     
      
  • 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;
}
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top