Componente de autenticação LDAP para VRaptor 3

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:

  1. Consulta anônima para obter o DN do usuário a se autenticar;
  2. 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