Pregunta

Estoy escribiendo una aplicación web Spring que requiere que los usuarios inicien sesión.Mi empresa tiene un servidor Active Directory que me gustaría utilizar para este fin.Sin embargo, tengo problemas para usar Spring Security para conectarme al servidor.

Estoy usando Spring 2.5.5 y Spring Security 2.0.3, junto con Java 1.6.

Si cambio la URL LDAP a la dirección IP incorrecta, no genera una excepción ni nada, así que me pregunto si es igual. intentando para conectarse al servidor para empezar.

Aunque la aplicación web se inicia correctamente, cualquier información que ingrese en la página de inicio de sesión se rechaza.Anteriormente había usado InMemoryDaoImpl, que funcionó bien, por lo que el resto de mi aplicación parece estar configurada correctamente.

Aquí están mis beans relacionados con la seguridad:

  <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>
¿Fue útil?

Solución

Tuve la misma experiencia de golpearme la cabeza contra la pared que tú y terminé escribiendo un proveedor de autenticación personalizado que realiza una consulta LDAP en el servidor Active Directory.

Entonces mis beans relacionados con la seguridad son:

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

Luego la clase 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;
    }

}

Luego la clase 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 = "";
        }
    }

}

Y finalmente, la clase 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;
    }

}

Notarás que hay algunos bits allí que quizás no necesites.

Por ejemplo, mi aplicación necesitaba conservar el contexto LDAP de inicio de sesión exitoso para que el usuario pueda utilizarlo una vez que haya iniciado sesión; el propósito de la aplicación es permitir que los usuarios inicien sesión a través de sus credenciales de AD y luego realicen más funciones relacionadas con AD.Por eso, tengo un token de autenticación personalizado, LdapAuthenticationToken, que paso (en lugar del token de autenticación predeterminado de Spring) que me permite adjuntar el contexto LDAP.En LdapAuthenticationProvider.authenticate(), creo ese token y lo vuelvo a pasar;en LdapAuthenticatorImpl.authenticate(), adjunto el contexto de inicio de sesión al objeto devuelto para que pueda agregarse al objeto de autenticación Spring del usuario.

Además, en LdapAuthenticationProvider.authenticate(), asigno a todos los usuarios que han iniciado sesión el rol ROLE_USER; eso es lo que me permite probar ese rol en mis elementos de URL de intercepción.Querrá que esto coincida con cualquier rol que desee probar, o incluso asignar roles basados ​​en grupos de Active Directory o lo que sea.

Finalmente, y como corolario de esto, la forma en que implementé LdapAuthenticationProvider.authenticate() les da a todos los usuarios con cuentas AD válidas el mismo rol ROLE_USER.Obviamente, en ese método, puede realizar más pruebas en el usuario (es decir, ¿está el usuario en un grupo AD específico?) y asignar roles de esa manera, o incluso probar alguna condición antes incluso de otorgarle acceso al usuario en todo.

Otros consejos

Como referencia, Spring Security 3.1 tiene un proveedor de autenticación. específicamente para Active Directory.

Sólo para llevar esto a un estado actualizado.Spring Security 3.0 tiene un paquete completo con implementaciones predeterminadas dedicadas a ldap-bind, así como a consultar y comparar autenticación.

Pude autenticarme en el directorio activo usando Spring Security 2.0.4.

Documenté la configuración

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

Como en la respuesta anterior de Luke:

Spring Security 3.1 tiene un proveedor de autenticación específicamente para Active Directory.

Aquí se detalla cómo se puede hacer esto fácilmente usando ActiveDirectoryLdapAuthenticationProvider.

En recursos.groovy:

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

En Config.groovy:

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

Este es todo el código que necesitas.Prácticamente puedes eliminar todas las demás configuraciones de grails.plugin.springsecurity.ldap.* en Config.groovy, ya que no se aplican a esta configuración de AD.

Para obtener documentación, consulte:http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

La autenticación LDAP sin SSL no es segura. Cualquiera puede ver las credenciales del usuario cuando se transfieren al servidor LDAP.Sugiero usar el protocolo LDAPS:\ para la autenticación.No requiere ningún cambio importante en la parte de primavera, pero es posible que tenga algunos problemas relacionados con los certificados.Ver Autenticación LDAP de Active Directory en Spring con SSL para más detalles

De la respuesta anterior de Luke:

Como referencia, Spring Security 3.1 tiene un proveedor de autenticación [específicamente para Active Directory] [1].

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

Probé lo anterior con Spring Security 3.1.1:Hay algunos cambios leves con respecto a ldap: los grupos de directorio activo de los que el usuario es miembro aparecen como el caso original.

Anteriormente, en ldap, los grupos estaban en mayúscula y tenían el prefijo "ROLE_", lo que los hacía fáciles de encontrar con una búsqueda de texto en un proyecto, pero obviamente podría causar problemas en un grupo Unix si por alguna extraña razón tuviera 2 grupos separados solo diferenciados por mayúsculas y minúsculas ( es decir, cuentas y Cuentas).

Además, la sintaxis requiere la especificación manual del nombre y el puerto del controlador de dominio, lo que asusta un poco la redundancia.Seguramente hay una manera de buscar el registro DNS SRV para el dominio en Java, es decir, equivalente a (del cómo 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.

seguido de una búsqueda A normal:

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

(En realidad, es posible que también necesite buscar el registro SRV _kerberos...)

Lo anterior fue con Samba4.0rc1, estamos actualizando progresivamente del entorno LDAP Samba 3.x a Samba AD one.

Si estás usando primavera seguridad 4 También puede implementar el mismo usando la clase dada

  • 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;
}
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top