Boa noite a todos,
Depois de me bater um tempo na busca por vários sites por aí, consegui montar uma solução de autenticação LDAP que atendeu a minha necessidade aqui:
- Projeto WEB com VRaptor 3;
- Spring como contêiner de injeção de dependências;
- Regra de acesso, permissões e gerenciamento de usuários personalizada (sem Spring Security);
Bom, basicamente para não ficar muito perdido como eu fiquei, primeiramente é necessário que seja entendido como que funciona o LDAP, a sua estrutura básica da árvore dos contatos, ideia do serviço, e talz, acho que a página abaixo vai explicar melhor:
http://www.vivaolinux.com.br/artigo/Entendendo-o-LDAP
Basicamente, o que eu pensei inicialmente foi de usar o próprio Spring para fazer isso, assim procurei pelo pacote Spring-LDAP, só que acabei me enrolando um pouco quanto as suas dependências, inclusive a Spring-DAO que tinha achado nos repositórios Maven. Por fim procurei entender o que realmente queria no LDAP, que era apenas autenticar os usuários e obter os dados como nome e telefone através de um AJAX no formulário de cadastro do usuário (sincronizar com o servidor LDAP).
Desta forma achei uma solução feita em ‘Java puro’ ou seja, sem a necessidade de nenhum framework ou biblioteca adicional ao meu projeto, apenas usando os recursos do pacote ‘java.naming’.
Conceito
A autenticação do usuário no LDAP ocorre em duas etapas, a primeira é em você obter o Domain Name (DN) do usuário a partir de alguma informação do mesmo, uma que seja única como o login, o resultado que você terá será a posição do usuário na árvore de registros:
uid=carlos,ou=usuarios-correio,dc=carlos4web,dc=wordpress,dc=com
Onde o tronco da árvore começa pelo “br->inf->carlos->usuarios-correio->{uid:carlos}, isso numa leitura ou quem conhece sobre LDAP ficaria mais claro.
Seguindo com a implementação Java, primeiro obtemos o DN de onde está o usuário, depois re-conectamos ao servidor LDAP só que fazendo um acesso autenticado onde o login seria o DN obtido do usuário e a senha que o mesmo informou no formulário de login da nossa aplicação.
Até agora temos isso:
- Consulta anônima para obter o DN do usuário a se autenticar;
- Consulta autenticada com o DN obtido e a senha informada pelo usuário no sistema;
Codificação
A classe Usuario do nosso exemplo:
[java]
public class Usuario implements Serializable
{
private static final long serialVersionUID = 1L;
private String nome;
private String email;
private String telefone;
private String login;
private String senha;
}
[/java]
Para uma melhor leitura, os getters e setters não serão postados nos trechos de código, em anexo a este post deixo o download das classes do exemplo.
Para trabalhar com o resultado do método getUserDetails do componente eu uso uma classe estendendo da classe Usuario, a classe LDAPUserProxy, que recebe em seu construtor um objeto javax.naming.directory.SearchResult como dependência direta no construtor. Assim ao ser instanciado passamos o resultado da consulta e o objeto já estará com os dados populados no bean Usuario. Classe LDAPUserProxy:
[java]
public class LDAPUserProxy extends Usuario
{
private SearchResult result;
public LDAPUserProxy(SearchResult result) throws NamingException
{
super();
this.result = result;
// inicia os dados diretamente.
this.setUserDetails();
}
private void setUserDetails() throws NamingException
{
if(this.result != null)
{
this.setNome ((String) this.get(“cn”));
this.setLogin ((String) this.get(“uid”));
this.setEmail ((String) this.get(“mail”));
}
else
{
throw new NamingException(“Nenhum usuário no SearchResult atual.”);
}
}
private Object get (String attrId) throws NamingException
{
return this.getResult().getAttributes().get(attrId).get();
}
}
[/java]
Por fim temos o nosso componente, que nos dá um front-end para essa lógica toda. Abaixo segue a estrutura do mesmo:
[java]
@br.com.caelum.vraptor.ioc.Component
public class LDAPComponent implements Serializable
{
private static final long serialVersionUID = -2115416453624593622L;
private static final Logger logger = Logger.getLogger(LDAPComponent.class);
private Result result;
private ServletContext context;
public LDAPComponent(Result result, ServletContext context) throws NamingException
{
super();
this.result = result;
this.context = context;
}
public Hashtable<String, String> getBasicAccessLdapConfiguration (ServletContext context)
{
logger.info(“Configurando a instância do LDAPAuthComponent”);
Hashtable<String, String> propriedades = new Hashtable<String, String>(3);
propriedades.put(Context.INITIAL_CONTEXT_FACTORY, context.getInitParameter(“ldap.factory”));
propriedades.put(Context.PROVIDER_URL, context.getInitParameter(“ldap.address”));
propriedades.put(Context.SECURITY_AUTHENTICATION, “none”);
return propriedades;
}
private String getLDAPDomainName ()
{
return this.context.getInitParameter(“ldap.dn”);
}
public LDAPUserProxy getUserDetails (Usuario usuario) throws NamingException
{
if(usuario != null && usuario.getLogin() != “”)
{
LDAPUserProxy proxy = null;
try
{
Hashtable<String, String> propriedades = this.getBasicAccessLdapConfiguration(context);
DirContext dir = new InitialDirContext(propriedades);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration<SearchResult> result = dir.search(this.getLDAPDomainName(), “uid=” + usuario.getLogin(), controls);
while (result.hasMoreElements())
{
SearchResult sr = (SearchResult) result.nextElement();
proxy = new LDAPUserProxy(sr);
// Somente um.
break;
}
dir.close();
} catch (NamingException e) {
e.printStackTrace();
}
return proxy;
}
else
{
return null;
}
}
public String getLDAPUserDomainName (Usuario usuario)
{
if(usuario == null)
{
return null;
}
else
{
logger.info(“Obtendo o User Domain User.”);
String userDomainName = null;
try
{
Hashtable<String, String> propriedades = this.getBasicAccessLdapConfiguration(context);
DirContext dir = new InitialDirContext(propriedades);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration<SearchResult> result = dir.search(this.getLDAPDomainName(), “uid=” + usuario.getLogin(), controls);
while (result.hasMoreElements())
{
SearchResult sr = (SearchResult) result.nextElement();
userDomainName = sr.getNameInNamespace();
// Somente um.
break;
}
dir.close();
} catch (NamingException e) {
e.printStackTrace();
}
return userDomainName;
}
}
public LDAPUserProxy authenticate (Usuario usuario)
{
if(usuario == null || usuario.getLogin() == null || usuario.getSenha() == null)
{
logger.info(“O login ou senha do usuário podem estar nulos, retornando NULL.”);
return null;
}
else
{
logger.info(“Iniciando a autenticação do usuário LDAP: ” + usuario.getLogin());
String userDomainName = this.getLDAPUserDomainName(usuario);
if(userDomainName == null)
{
logger.info(“Nenhum DN encontrado para o login [” + usuario.getLogin() + “]”);
return null;
}
else
{
logger.info(“DN encontrado [” + userDomainName + “] iniciando a validação da senha no LDAP.”);
// Configuro a busca autenticada ao banco do LDAP.
LDAPUserProxy proxy = null;
try
{
logger.info(“Autenticando o usuário no DN: ” + userDomainName);
Hashtable<String, String> propriedades = this.getBasicAccessLdapConfiguration(context);
// Adiciona a autenticação do usuário no LDAP. Caso a senha não seja
// a correta, uma exception será disparada.
propriedades.put(Context.SECURITY_AUTHENTICATION, “simple”);
propriedades.put(Context.SECURITY_PRINCIPAL, userDomainName);
propriedades.put(Context.SECURITY_CREDENTIALS, usuario.getSenha());
DirContext dir = new InitialDirContext(propriedades);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration<SearchResult> result = dir.search(userDomainName, “uid=” + usuario.getLogin(), controls);
while (result.hasMoreElements())
{
SearchResult sr = (SearchResult) result.nextElement();
logger.info(“Carregando o usuário LDAP: ” + sr.getAttributes());
proxy = new LDAPUserProxy(sr);
break;
}
dir.close();
} catch (Exception e) {
logger.info(“Exception na autenticação: ” + e.getLocalizedMessage());
e.printStackTrace();
}
return proxy;
}
}
}
public Result getResult() {
return result;
}
public ServletContext getContext() {
return context;
}
}
[/java]
Conclusão
O maior problema que tive para desenvolver esse componente foi basicamente em entender a lógica do modo de usar o LDAP já o outro porém era achar uma referência Java de como manipular o LDAP.
Enfim, este é o componente que eu fiz para resolver o problema que eu tinha para integrar o acesso dos usuários aqui da empresa por meio do serviço LDAP existente.
Dica
Para quem está implementando agora o LDAP, vale lembrar que você precisa ter um usuário local na sua aplicação, ou seja, o usuário Carlos do LDAP deve estar cadastrado no seu banco de dados, só que sem senha por exemplo, porque no LDAP não tem como filtrar quem vai acessar a sua aplicação, ou seja, todos os usuários da árvore do LDAP poderiam acessar. E também por regra de banco de dados relacional, você precisa ter o usuário localmente no banco para fazer as referências de login, relacionamento, etc.
Código fonte
http://www.mediafire.com/file/fpv8v1xxf64ptcx/ldap-tutorial.zip