Pergunta

Eu estou escrevendo uma aplicação web de Primavera que exige que os usuários login. Minha empresa tem um servidor de Active Directory que eu gostaria de fazer uso para essa finalidade. No entanto, estou tendo problemas para usar Spring Security para se conectar ao servidor.

Eu estou usando Spring 2.5.5 e Spring Security 2.0.3, juntamente com Java 1.6.

Se eu mudar a URL LDAP para o endereço IP errado, ele não lançar uma exceção ou qualquer coisa, então eu estou querendo saber se é mesmo tentando para se conectar ao servidor para começar.

Embora a aplicação web inicia-se muito bem, qualquer informação que eu entrar na página de login é rejeitada. Eu já tinha usado um InMemoryDaoImpl, que funcionou bem, então o resto da minha aplicação parece estar configurado corretamente.

Aqui estão os meus feijão relacionados à segurança:

  <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>
Foi útil?

Solução

Eu tive o mesmo bater-meu-cabeça-contra-o-parede experiência que você fez, e acabou escrevendo um provedor de autenticação personalizada que faz uma consulta LDAP contra o servidor Active Directory.

Então, meu feijão de segurança relacionadas são:

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

Em seguida, a 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;
    }

}

Em seguida, a 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, finalmente, a 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;
    }

}

Você vai notar que existem alguns pedaços lá dentro que você não pode precisar.

Por exemplo, meu aplicativo necessário para manter o sucesso logados contexto LDAP para uso posterior pelo usuário uma vez logado - o objetivo do aplicativo é permitir que os usuários fazem login via suas credenciais AD e, em seguida, realizar mais AD-relacionados funções. Então, por causa disso, eu tenho um token de autenticação personalizada, LdapAuthenticationToken, que eu passar em torno (em vez de autenticação de token padrão da Primavera), que me permite anexar o contexto LDAP. Em LdapAuthenticationProvider.authenticate (), eu criar esse token e passá-lo para fora; em LdapAuthenticatorImpl.authenticate (), eu anexar o registado no contexto para o objeto de retorno para que ele possa ser adicionado ao objeto de autenticação Primavera do usuário.

Além disso, em LdapAuthenticationProvider.authenticate (), atribuir todos os usuários conectados ao papel ROLE_USER - isso é o que me permite, em seguida, teste para esse papel em meus elementos de interceptação-url. Você vai querer fazer este jogo qualquer papel que você quer para testar, ou mesmo papéis atribuir com base em grupos ou qualquer outra coisa do Active Directory.

Finalmente, e um corolário de que, do jeito que eu implementado LdapAuthenticationProvider.authenticate () dá a todos os usuários com AD válido contas o mesmo papel ROLE_USER. Obviamente, nesse método, você pode realizar mais testes sobre o usuário (ou seja, é o usuário em um grupo de anúncios específico?) E os papéis atribuir esse caminho, ou o teste mesmo para alguma condição antes mesmo de conceder o acesso do usuário em todos .

Outras dicas

Para referência, Spring Security 3.1 tem um provedor de autenticação especificamente para Diretório ativo.

Apenas para trazer isso para um status up-to-date. Spring Security 3.0 tem um pacote completo com implementações padrão dedicados a ldap-bind, bem como de consulta e comparação de autenticação.

Eu era capaz de autenticar contra o Active Directory usando segurança Primavera 2.0.4.

Eu documentei as configurações

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

Como em resposta de Lucas acima:

Spring Security 3.1 tem um provedor de autenticação especificamente para Active Directory.

Aqui está o detalhe de como isso pode ser feito facilmente usando ActiveDirectoryLdapAuthenticationProvider.

Em resources.groovy:

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

Em Config.groovy:

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

Este é todo o código que você precisa. Você pode muito bem remover todos os outros grails.plugin.springsecurity.ldap. * Configurações no Config.groovy como eles não se aplicam a esta configuração AD.

Para a documentação, ver: http: / /docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

autenticação LDAP sem SSL não é ninguém segura pode ver a credencial do usuário quando aqueles são transffered ao servidor LDAP. Eu sugiro usar LDAPS: \ protocolo para autenticação. Ele não requer qualquer alteração importante na parte da primavera, mas você pode correu com algumas questões relacionadas com os certificados. Veja a autenticação LDAP Active Directory na Primavera com SSL para mais detalhes

De resposta de Lucas acima:

Para referência, Spring Security 3.1 tem um provedor de autenticação [Especificamente para Active Directory] [1].

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

Eu tentei o acima com Spring Security 3.1.1: existem algumas pequenas alterações de ldap - os grupos do Active Directory o usuário é um membro de vir através como embalagem original

.

Anteriormente, ao abrigo ldap os grupos foram capitalizados e prefixado com "ROLE_", o que os tornava fácil de encontrar com uma pesquisa de texto em um projeto, mas, obviamente, problemas caso poderia em um grupo unix se por algum motivo estranho tinha 2 grupos separados única diferenciados a caso (ou seja, contas e Contas).

Além disso, a sintaxe exige a especificação manual do nome do controlador de domínio e porto, o que torna um pouco assustador para redundância. Certamente há uma maneira de olhar para cima o registro SRV DNS para o domínio em java, ou seja, equivalente a (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.

seguido de pesquisa regular de A:

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

(Na verdade talvez seja necessário pesquisar _kerberos registro SRV também ...)

O texto acima foi com Samba4.0rc1, estamos progressivamente atualizando de Samba ambiente 3.x LDAP para Samba AD um.

Se você estiver usando Spring de segurança de 4 você também pode implementar mesmo usando determinada classe

  • 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top