Domanda

Sto scrivendo un'applicazione web Spring che richiede agli utenti di accedere.La mia azienda dispone di un server Active Directory che vorrei utilizzare a questo scopo.Tuttavia, ho problemi con Spring Security per connettermi al server.

Utilizzo Spring 2.5.5 e Spring Security 2.0.3, insieme a Java 1.6.

Se cambio l'URL LDAP con l'indirizzo IP sbagliato, non viene generata un'eccezione o altro, quindi mi chiedo se sia anche provando per connettersi al server per cominciare.

Anche se l'applicazione web si avvia correttamente, tutte le informazioni che inserisco nella pagina di accesso vengono rifiutate.In precedenza avevo utilizzato InMemoryDaoImpl, che funzionava bene, quindi il resto della mia applicazione sembra essere configurato correttamente.

Ecco i miei bean relativi alla sicurezza:

  <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>
È stato utile?

Soluzione

Ho avuto la tua stessa esperienza di sbattere la testa contro il muro e ho finito per scrivere un provider di autenticazione personalizzato che esegue una query LDAP sul server Active Directory.

Quindi i miei bean relativi alla sicurezza sono:

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

Quindi 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;
    }

}

Quindi 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 = "";
        }
    }

}

E infine, 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;
    }

}

Noterai che ci sono alcuni pezzi di cui potresti non aver bisogno.

Ad esempio, la mia app doveva conservare il contesto LDAP effettuato con successo per un ulteriore utilizzo da parte dell'utente una volta effettuato l'accesso: lo scopo dell'app è consentire agli utenti di accedere tramite le proprie credenziali AD e quindi eseguire ulteriori funzioni relative ad AD.Per questo motivo, ho un token di autenticazione personalizzato, LdapAuthenticationToken, che passo (invece del token di autenticazione predefinito di Spring) che mi consente di allegare il contesto LDAP.In LdapAuthenticationProvider.authenticate(), creo quel token e lo passo indietro;in LdapAuthenticatorImpl.authenticate(), allego il contesto di accesso all'oggetto restituito in modo che possa essere aggiunto all'oggetto di autenticazione Spring dell'utente.

Inoltre, in LdapAuthenticationProvider.authenticate(), assegno a tutti gli utenti che hanno effettuato l'accesso il ruolo ROLE_USER: questo è ciò che mi consente di testare quel ruolo nei miei elementi intercetta-url.Ti consigliamo di fare in modo che corrisponda al ruolo che desideri testare o addirittura assegnare ruoli in base ai gruppi di Active Directory o altro.

Infine, e come corollario, il modo in cui ho implementato LdapAuthenticationProvider.authenticate() offre a tutti gli utenti con account AD validi lo stesso ruolo ROLE_USER.Ovviamente, con questo metodo è possibile eseguire ulteriori test sull'utente (ad esempio, l'utente fa parte di un gruppo AD specifico?) e assegnare ruoli in questo modo, o anche testare alcune condizioni prima ancora di concedere all'utente l'accesso a Tutto.

Altri suggerimenti

Per riferimento, Spring Security 3.1 dispone di un provider di autenticazione specifico per Active Directory.

Giusto per portarlo ad uno stato aggiornato.Spring Security 3.0 ha a pacchetto completo con implementazioni predefinite dedicate al collegamento ldap nonché all'esecuzione di query e confronti di autenticazione.

Sono stato in grado di autenticarmi su Active Directory utilizzando Spring Security 2.0.4.

Ho documentato le impostazioni

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

Come nella risposta di Luke sopra:

Spring Security 3.1 dispone di un provider di autenticazione specifico per Active Directory.

Ecco i dettagli su come eseguire facilmente questa operazione utilizzando ActiveDirectoryLdapAuthenticationProvider.

In risorse.groovy:

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

In Config.groovy:

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

Questo è tutto il codice di cui hai bisogno.Puoi praticamente rimuovere tutte le altre impostazioni di grails.plugin.springsecurity.ldap.* in Config.groovy poiché non si applicano a questa configurazione di AD.

Per la documentazione vedere:http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

L'autenticazione LDAP senza SSL non è sicura: chiunque può vedere le credenziali dell'utente quando vengono trasferite al server LDAP.Suggerisco di utilizzare il protocollo LDAPS:\ per l'autenticazione.Non richiede alcuna modifica importante alla parte primaverile, ma potresti riscontrare alcuni problemi relativi ai certificati.Vedere Autenticazione LDAP Active Directory in primavera con SSL per ulteriori dettagli

Dalla risposta di Luke sopra:

Per riferimento, Spring Security 3.1 ha un fornitore di autenticazione [specificamente per Active Directory] [1].

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

Ho provato quanto sopra con Spring Security 3.1.1:ci sono alcune lievi modifiche rispetto a ldap: i gruppi di Active Directory di cui l'utente è membro vengono presentati come caso originale.

Precedentemente sotto ldap i gruppi erano in maiuscolo e avevano il prefisso "ROLE_", il che li rendeva facili da trovare con una ricerca di testo in un progetto ma ovviamente potevano causare problemi in un gruppo unix se per qualche strano motivo avevano 2 gruppi separati differenziati solo per case( cioè conti e conti).

Inoltre, la sintassi richiede la specifica manuale del nome e della porta del controller di dominio, il che rende un po' preoccupante la ridondanza.Sicuramente esiste un modo per cercare il record DNS SRV per il dominio in Java, ovvero equivalente a (da 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.

seguito dalla normale ricerca A:

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

(In realtà potrebbe essere necessario cercare anche il record SRV _kerberos...)

Quanto sopra era con Samba4.0rc1, stiamo progressivamente aggiornando dall'ambiente LDAP Samba 3.x a quello Samba AD.

Se usi Spring sicurezza 4 Puoi anche implementare lo stesso usando la classe data

  • 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;
}
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top