MBA in JAVA, SOA and Internet of Things at FIAP. MBA Systems Engineering at ESAB. 10 years experience in business analysis and web application.
Publicado em 21/10/2026
Em empresas de médio e grande porte, os usuários já estão cadastrados em algum lugar: o diretório corporativo. É nele, normalmente um Active Directory ou um OpenLDAP, que ficam o login de rede, a senha, o departamento e os grupos de cada colaborador. Mesmo assim, muitas aplicações internas ainda nascem com uma tabela própria de usuários e senhas.
Neste artigo, mostro como fazer uma aplicação Spring Boot autenticar usuários direto em um servidor LDAP usando o Spring Security. A base é o guia oficial Authenticating a User with LDAP, do Spring, com três acréscimos: os conceitos de LDAP para quem nunca trabalhou com eles, os grupos do diretório como papéis da aplicação e o que muda quando o exemplo chega a um Active Directory de verdade.
Imagine um portal interno de RH que 3 mil colaboradores usam para consultar holerites e pedir férias. A primeira versão saiu às pressas, com uma tabela usuario no banco e uma tela de cadastro. Funciona bem até o primeiro desligamento.
Quando alguém sai da empresa, a TI desativa a conta no Active Directory, e e-mail, VPN e rede param de funcionar na hora. Só que o portal de RH não fica sabendo. O ex-colaborador continua com acesso até alguém lembrar de bloquear a conta manualmente.
Esse cenário tem vários custos:
A saída é a aplicação deixar de guardar senhas e passar a autenticação para quem já é a fonte oficial dos usuários: o diretório corporativo, acessado via LDAP.
LDAP (Lightweight Directory Access Protocol) é um protocolo para consultar e manter serviços de diretório. Pense no diretório como um banco de dados organizado em árvore e otimizado para leitura, onde ficam pessoas, grupos, computadores e permissões. Active Directory (Microsoft), OpenLDAP e 389 Directory Server são implementações conhecidas.
Alguns termos aparecem o tempo todo:
| Termo | O que é | Exemplo |
|---|---|---|
| DIT (Directory Information Tree) | A árvore de entradas do diretório | a figura abaixo |
| DN (Distinguished Name) | O caminho completo e único de uma entrada | uid=ben,ou=people,dc=springframework,dc=org |
| Base DN | A raiz a partir da qual a aplicação trabalha | dc=springframework,dc=org |
| OU (Organizational Unit) | Uma “pasta” para organizar entradas | ou=people, ou=groups |
| Bind | A operação de se autenticar no servidor LDAP com um DN e uma senha | — |
| LDIF (LDAP Data Interchange Format) | Formato texto para importar e exportar entradas | o arquivo schema.ldif do exemplo |
Leia o DN de trás para frente: dentro da organização springframework.org, na unidade people, está o usuário ben. É essa estrutura que vamos ensinar a aplicação a navegar.
A figura no início do artigo resume o fluxo em quatro passos. O usuário envia as credenciais para /login, o filtro do Spring Security as entrega ao AuthenticationManager, o LdapAuthenticationProvider valida a senha no diretório e, se configurado, os grupos do usuário viram papéis (roles) da aplicação.
A decisão de arquitetura mais importante está na validação da senha, porque o Spring Security oferece duas estratégias:
LdapPasswordComparisonAuthenticationManagerFactory): a aplicação lê a entrada do usuário e compara a senha digitada com o atributo userPassword. Com um PasswordEncoder como o BCrypt, essa comparação acontece dentro da aplicação, que precisa ter permissão para ler o hash da senha no diretório.LdapBindAuthenticationManagerFactory): a aplicação tenta se autenticar no LDAP com o DN e a senha do próprio usuário. Se o servidor aceitar, a senha está correta. A aplicação nunca vê o hash.O guia oficial usa comparação de senha, que funciona muito bem com o servidor embarcado. Em produção, como veremos adiante, o bind costuma ser o caminho certo.
O exemplo usa Java 17 ou superior, Spring Boot 4 e Maven. O servidor LDAP é o UnboundID, escrito em Java, que o Spring Boot sobe embarcado junto com a aplicação. Assim dá para testar tudo sem instalar nada.
Gere um projeto no Spring Initializr com a dependência Spring Web e crie um @RestController com um GET / que devolve a mensagem Welcome to the home page!. Rode com ./mvnw spring-boot:run e acesse http://localhost:8080: qualquer pessoa vê a mensagem. É esse acesso aberto que vamos proteger.
No pom.xml, adicione:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
Não é preciso declarar versões, porque o spring-boot-starter-parent cuida delas. O unboundid-ldapsdk traz o servidor embarcado e só faz sentido em desenvolvimento e testes.
@Configuration
public class WebSecurityConfig {
@Bean
public AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory =
new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, new BCryptPasswordEncoder());
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
}
O que acontece aqui:
BaseLdapPathContextSource é a conexão com o LDAP. O Spring Boot cria essa conexão sozinho a partir do application.properties, já apontando para o servidor embarcado.setUserDnPatterns("uid={0},ou=people") ensina como montar o DN: o {0} é trocado pelo usuário digitado. O padrão é relativo à base DN.BCryptPasswordEncoder é necessário porque as senhas do arquivo LDIF estão em BCrypt.Repare que não declaramos nenhuma regra de acesso. Sem um SecurityFilterChain próprio, o Spring Boot aplica o padrão: todas as rotas exigem autenticação e o formulário de login fica em /login.
spring.ldap.base=dc=springframework,dc=org
spring.ldap.embedded.base-dn=${spring.ldap.base}
spring.ldap.embedded.port=8389
A primeira linha define a base DN usada pela aplicação. A segunda define a base do servidor embarcado, reaproveitando a primeira para evitar duplicação. A terceira define a porta em que o servidor embarcado escuta.
O servidor embarcado carrega automaticamente o arquivo src/main/resources/schema.ldif. Este é o trecho do usuário ben:
dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: $2a$10$c6bSeWPhg06xB1lvmaWNNe4NROmZiSpYhlocU/98HNr2MhIOiSt36
O arquivo completo, com a raiz, as unidades people e groups, o usuário bob e o grupo developers, está no repositório do guia. Use LDIF apenas para desenvolvimento e testes. Em produção, os usuários vêm do diretório real.
Acesse http://localhost:8080 e o Spring Security vai redirecionar você para /login. Entre com o usuário ben e a senha benspassword e a mensagem aparece.
Com a configuração do guia, o ben consegue entrar, mas sem nenhum papel. O guia autentica, mas não autoriza. Na prática, quase toda aplicação precisa saber se o usuário é do RH, do financeiro ou da equipe de desenvolvimento.
Para trazer os grupos do diretório, adicione um authorities populator antes do createAuthenticationManager():
DefaultLdapAuthoritiesPopulator authorities =
new DefaultLdapAuthoritiesPopulator(contextSource, "ou=groups");
authorities.setGroupSearchFilter("(uniqueMember={0})");
factory.setLdapAuthoritiesPopulator(authorities);
O Spring busca, dentro de ou=groups, os grupos em que o DN do usuário aparece como uniqueMember. O grupo cn=developers vira a autoridade ROLE_DEVELOPERS, e a partir daí as regras de acesso usam papéis, como hasRole("DEVELOPERS"), em vez de listas de usuários espalhadas pelo código.
Voltando ao portal de RH, em produção o diretório é um Active Directory acessado por LDAPS. Aqui a comparação de senha do guia não funciona, porque o AD não permite ler a senha (o atributo unicodePwd só aceita escrita). O caminho é o bind.
Primeiro, troque o servidor embarcado pelo diretório real:
spring.ldap.urls=ldaps://ad.empresa.com.br:636
spring.ldap.base=dc=empresa,dc=com,dc=br
spring.ldap.username=${LDAP_USUARIO_SERVICO}
spring.ldap.password=${LDAP_SENHA_SERVICO}
O username e o password são de uma conta de serviço com permissão apenas de leitura, usada para encontrar o usuário e os grupos dele. Depois, troque a fábrica de comparação pela de bind:
LdapBindAuthenticationManagerFactory factory =
new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserSearchBase("ou=people");
factory.setUserSearchFilter("(uid={0})");
return factory.createAuthenticationManager();
Em vez de montar o DN por padrão, a aplicação busca o usuário e depois faz o bind com a senha digitada. No Active Directory, o filtro costuma ser (sAMAccountName={0}), e o populator de grupos continua valendo, com o filtro padrão do Spring, (member={0}). Para quem usa só AD, o Spring Security também oferece o ActiveDirectoryLdapAuthenticationProvider, especializado nesse cenário.
Com exceção da conexão com o Active Directory, as configurações deste artigo foram validadas com testes automatizados contra o servidor embarcado, incluindo a variante com bind, o mapeamento de grupos e o erro de DN descrito mais abaixo.
Faz sentido quando:
Prefira outra abordagem quando:
Vantagens: uma única fonte de verdade para os usuários, desligamento centralizado, nenhuma senha armazenada na aplicação, reaproveitamento dos grupos como papéis e implementação curta com Spring.
Desvantagens: a aplicação ainda recebe a senha do usuário, a disponibilidade do login passa a depender do diretório, cada login faz uma ida ao LDAP, não há MFA nativo e a configuração fica acoplada à estrutura de OUs da empresa.
userSearchBase específica em vez da raiz do domínio. Buscas menores são mais rápidas e evitam ambiguidade.<scope>test</scope>.formLogin() e authenticated(), do spring-security-test, contra o servidor embarcado.spring.ldap.base definido, o padrão é uid={0},ou=people. Se você colocar uid={0},ou=people,dc=springframework,dc=org, o Spring procura um DN com a base duplicada e nenhum login funciona.(member={0}), mas grupos do tipo groupOfUniqueNames usam uniqueMember. O sintoma é o usuário conseguir entrar, mas receber 403 em todas as telas protegidas por papel.ROLE_. O grupo developers vira ROLE_DEVELOPERS. Use hasRole("DEVELOPERS"), sem o prefixo, ou hasAuthority("ROLE_DEVELOPERS"), com o prefixo.application.properties.O guia oficial é ótimo para entender o mecanismo, mas resolve só metade do problema. Sem o mapeamento de grupos, todo usuário do diretório entra com os mesmos poderes, e isso raramente é o que uma aplicação real precisa.
A segunda lição é que escolher entre comparação de senha e bind é uma decisão de arquitetura, e não um detalhe de implementação. Essa escolha define quem enxerga o hash da senha e se a solução vai funcionar quando encontrar o Active Directory. Vale fazer essa escolha no desenho da solução, e não descobrir no primeiro deploy.
Por fim, o servidor embarcado é excelente para testes automatizados de segurança: login, senha errada e papéis passam a ser validados a cada build, e não à mão uma vez por release.
Autenticar no LDAP com Spring Boot exige pouco código: um AuthenticationManager, três propriedades e, no desenvolvimento, um arquivo LDIF. Em troca, a aplicação deixa de guardar senhas, herda o ciclo de vida dos usuários do diretório corporativo e passa a autorizar por grupos que a própria empresa já mantém.
O ponto de atenção é não levar para produção a configuração exatamente como está no guia. Use bind, LDAPS, uma conta de serviço com privilégio mínimo e grupos mapeados para papéis. E, se a empresa tiver um provedor de identidade com SSO, considere o LDAP direto na aplicação como um passo intermediário, e não como o destino final.
Se você já integrou alguma aplicação com Active Directory ou OpenLDAP, me conte quais desafios encontrou, pelo LinkedIn ou nos comentários da publicação nas redes sociais.