ASP.NET: Autenticación hacia Active Directory lenta la primera vez

Una pregunta que he escuchado regularmente al utilizar ASP.NET como framework de programación en sitios Web está relacionada con retraso en la autenticación hacia un controlador de dominio Windows utilizando el namespace System.DirectoryServices. En concreto me preguntan si he experimentado un pequeño retraso al momento del primer intento de autenticación, añadiendo que para los subsecuentes intentos en un cierto periodo de tiempo, esto deja de suceder y la verificación ya se efectua rápidamente.

Problema

En la mayoría de los casos el culplable es la clase DirectorySearcher que utilizamos erroneamente para realizar la busqueda del usuario que se quiere autenticar. Digo erroneamente, porque realmente no queremos buscar un usuario, sino unicamente antenticarlo. Aqui un código de autenticación muy común en la red con este problema del delay al primer intento.

[csharp]
public bool UsuarioAD(string usuario, string password, string strDominio, string DominioIp)
{
bool res = false;
try
{
string DirectoryEnt = "LDAP://" + DominioIp;
string dominiousuario = strDominio + @"\" + usuario;
DirectoryEntry Entry = new DirectoryEntry(DirectoryEnt, dominiousuario, password, AuthenticationTypes.None);
DirectorySearcher Search = new DirectorySearcher(Entry);
Search.Filter = "(SAMAccountName=" + usuario + ")";
Search.PropertiesToLoad.Add("cn");
SearchResult result = Search.FindOne();
res = true;
}
catch
{
// No hubo éxito
}
return res;
}
[/csharp]

SOLUCIÓN

La solución radica en entender que lo que deseamos es autenticar al usuario y no buscarlo por todo el directorio. Un código que sólo auntentifica y en caso de éxito devuelve el objeto nativo de interfaces de servicios de Active Directory (ADSI) es una buena forma de hacerlo.

[csharp]
private bool UsuarioAD(string usuario, string password, string strDominio, string DominioIp)
{
bool res = false;
try
{
string DirectoryEnt = "LDAP://" + DominioIp;
string dominiousuario = ddlDominio.SelectedValue + @"\" + usuario;
DirectoryEntry Entry = new DirectoryEntry(DirectoryEnt, dominiousuario, password, AuthenticationTypes.None);
object nativeObject = Entry.NativeObject;
res = true;
}
catch
{
//No hubo éxito
}
return res;
}
[/csharp]

En el caso de que desearamos realmente buscar por el árbol de AD, la recomendación está en definir adecuadamente la propiedad filter pues es la que puede alentar mucho este proceso. El tip está en NUNCA utilizar el asterisco (comodín) a la izquierda en la cadena de filtro del objeto DirectorySearcher. Aquí un ejemplo:

[csharp]
private bool Busca_Usuario_AD(string dominio, string usuario, string DominioIp)
{
string DirectoryEnt;
bool res = false;
try
{
DirectoryEnt = "LDAP://" + DominioIp + ":389";
SearchResultCollection results;
System.DirectoryServices.DirectoryEntry Entry = new System.DirectoryServices.DirectoryEntry(DirectoryEnt, "DOMINIO\\cuenta_para_buscar", "password", AuthenticationTypes.None);
DirectorySearcher Search = new DirectorySearcher(Entry);
// Observa que no debes de poner el asterisco a la izquierda de lo que buscas. Esto lo alenta.
Search.Filter = "(&(objectCategory=person)(|(cn=" + usuario + "*)(SAMAccountName=" + usuario + "*)))";
Search.PropertiesToLoad.Add("cn");
Search.PropertiesToLoad.Add("mail");
Search.SizeLimit = 20;
Search.ServerTimeLimit = DateTime.Now.AddSeconds(1) – DateTime.Now;
results = Search.FindAll();
if (results != null)
{
foreach (SearchResult result in results)
{
if (result.Properties["cn"].Count > 0)
{
res = true;
// Haces cosas con (string)result.Properties["cn"][0];
}
}
}
}
catch
{
//No hubo éxito
}
return res;
}
[/csharp]

Espero estos pequeños tips te sean de utilidad.