viernes, 12 de marzo de 2010

Enlace y Autenticación PHP con LDAP (LDAPhp.class.php)


Es un clásico escuchar los intentos de realizar este enlace (bind) y que no funcione porque no sabemos como configurar la cadena de conexión del usuario enlace o el usuario a autenticar. A esta cadena de conexión la llamamos Distinguished Name/Nombre distinguido y un ejemplo de DN es:

DN: cn=Andres Perez Albela, dc=empresa, dc=com

Siendo su RDN (Relative Distinguished Name/Nombre Distinguido Relativo) la siguiente:

RDN: cn=Andres Perez Albela

Esto quiere decir que el usuario es Andres Perez Albela y que el dominio es empresa.com, y me parece bueno resaltar que el Directorio Activo (AD) separa mediante la variable DC (Domain Controller//Controlador de Dominio) cada parte del dominio explotada por el símbolo "." Es decir, si hacemos explode('.', "empresa.com.pe") obtendremos tres partes en el arreglo, y cada parte del arreglo es un DC, siendo en este caso algo así:

DC=empresa, DC=com, DC=pe

Ahora podrán ver un ejemplo de Active Directory que hice para poder explicarles el enlace.


Imagen de Estructura de Active Directory


Como podemos ver, el dominio es: labserver.com y las unidades organizacionales (OU) son:

-Casa
--Conexiones
--Cuarto 1
--Cuarto 2

Siendo Casa el OU Principal y Conexiones, Cuarto 1, Cuarto 2 las sub-unidades organizacionales de Casa (Ojo que no dejan de ser OU).


Podemos apreciar el "samAccountName" de un usuario ingresando a esa opción


Dentro de Cuarto 1 tenemos un usuario con los siguientes datos:

CN : Andres Perez Albela H.
samAccountName : aperezalbela

La historia es que el desarrollador de un sistema creado en PHP quiere autenticar a sus usuarios con LDAP. Entonces crea una clase para realizar el enlace, pero este enlace no funcionará si no se crea un usuario enlace en el Directorio Activo, que en este caso es PHP y se encuentra en CASA/Conexiones, entonces hay que crear una cadena de conexión para ese usuario:

CN=PHP,OU=Conexiones,OU=Casa,DC=labserver,DC=com

Luego de haber autenticado al usuario PHP, se procede a buscar al usuario aperezalbela en el AD, entonces tenemos que crear una cadena para ubicarnos en la OU principal y poder hacer búsquedas con ldap_search que busca en la OU principal y bajo ella (sub-OU). La cadena para este caso es:

OU=Casa, DC=labserver, DC=com

De esta manera al desarrollador solo le quedará crear un filtro de búsqueda para comprobar la existencia del usuario aperezalbela. El filtro es:

(&(objectClass=user)(samAccountName=$usuario))

Como podemos ver, tenemos la variable $usuario que es el usuario a buscar y que recibiéndola por un formulario y habiéndose interpretado por php pues quedaría así:

(&(objectClass=user)(samAccountName=aperezalbela))

Para finalmente intentar realizar el enlace (autenticación de ese usuario en LDAP) con la contraseña enviada por el usuario en el formulario web.

Ahora presento el código PHP para la autenticación (LDAPhp.php).

<?php

//Importamos el manejador LDAP
require_once 'LDAPhp.class.php';

//Datos del Usuario a Autentificar con tu aplicacion en PHP
//La mayoría de veces estos datos no son una constante, sino valores recibidos por $_POST[]
$usuario = "aperezalbela";
$clave = "prueba";

//Datos de Configuración del Servidor
$servidor="192.168.1.2";
$puerto="389";

//Datos de Configuración para la conexión al LDAP (DN == "Distinguished Name")
$cadenaConexionUsuario = "CN=PHP,OU=Conexiones,OU=Casa,DC=labserver,DC=com";

//Datos de Configuración de usuario enlace LDAP-PHP, en este caso PHP (CN=PHP)
$password = "pruebaLdap";

//Datos de Configuración para el enlace al LDAP (DN == "Distinguished Name")
//En este caso nos situamos en el OU (Unidad Organizacional) principal
$dn = "OU=Casa, DC=labserver, DC=com";

//Datos de Configuración de Filtro para la búsqueda de un Usuario
$filtro = "(&(objectClass=user)(samAccountName=$usuario))";

//Conectamos al servidor LDAP con los => "Datos de Configuración del Servidor"
$PHPLdap = new PHPLdap($servidor, $puerto);

//Luego de habernos conectado al servidor, enlazamos la conexion identificandonos con un password
//para el usuario PHP (CN=PHP)
$PHPLdap->enlazarPHPLdap($cadenaConexionUsuario, $password);

//Luedo de haber realizado el enlace principal con el usuario PHP (CN=PHP) creado para este fin
//Procedemos a realizar la búsqueda del usuario a autenticar con la aplicación en PHP.
//PD: No se envía el usuario como parámetro ya que se encuentra interpretado en la variable: $filtro
$autenticar = $PHPLdap->autenticarUsuario($dn, $filtro, $clave);

//Verificamos el valor de retorno de la autenticación
if($autenticar===true){
//Autenticado
//Aca es donde generas la $_SESSION, COOKIES, o como manejes la autenticación
//Luego redirecciónas, a la página privada, etc.
echo "- A buena hora! Lograste autentificar al usuario $usuario";
}
else{
//Acceso Denegado
echo "- Contraseña Incorrecta para el usuario $usuario";
}

//Cerramos la conexion a LDAP
$PHPLdap->cerrarConexion();
?>

Y también el código de la clase (LDAPhp.class.php)

/*** Título: Enlace PHP con LDAP para autenticar usuarios en aplicaciónes web
**** Autor: Andrés Pérez-Albela H.** Web: http://www.seguridadinformatica.org/
**** Blog: http://develsec.blogspot.com
**** Versión: 1.0
**** Lenguaje: PHP
**** Licencia: Copyright © SeguridadInformatica.Org
**** Creative Commons Atribución-Sin Obras Derivadas 3.0 Unported License
****/

class PHPLdap{
private $ds;
private $bind;

public function PHPLdap($host, $puerto){

if(!isset($this->ds)){
if($this->ds = (ldap_connect($host, $puerto))){
echo "- LDAP conectado a: ".$host."";
}
else
{
die("- No me puedo conectar con servidor LDAP");
}
}
}

public function enlazarPHPLdap($cadenaConexionUsuario, $password){
$this->bind = @ldap_bind($this->ds, $cadenaConexionUsuario, $password);

if($this->bind){
echo "- Se realizó el enlace principal satisfactoriamente con el usuario PHP";
}
else
{
die("- El enlace principal no se pudo llevar a cabo");
}
}

public function autenticarUsuario($dn, $filtro, $clave){
$busqueda=ldap_search($this->ds, $dn, $filtro);
$resultados = ldap_get_entries($this->ds, $busqueda);

//Habiendo buscado el usuario a autenticar con "ldap_search" y obteniendo resultados en
//un arreglo $resultados, con "ldap_get_entries", pasamos a condicionar la existencia
//de resultados positivos.

if($resultados["count"]>0){
//Ingreso a la condicional, verificando con el "count" que el usuario exista
//Quiere decir que si encontró al usuario
//Luego obtenemos el DN (Nombre Distinguido) del usuario

$dnUsuario = trim($resultados[0]["distinguishedname"][0]);

//Para poder finalmente realizar el enlace final, siendo "enlace final" el login correcto.
//Si y solo si... la clave es correcta

if(@ldap_bind($this->ds, $dnUsuario, $clave)){
//Se realiza el bind, siendo la clave correcta, y se retorna un valor VERDADERO (true)
return true;
}
else
{
return false;
}
}
}

public function cerrarConexion(){
//Cerramos la conexion a LDAP
ldap_close($this->ds);
}
}
?>

Bueno, y eso es todo. Espero que les sirva la información, el post es completamente propio y cualquier consulta no duden en comentarla acá en el post.

Saludos, hasta la próxima entrada.

17 comentarios:

Kenji Ykey dijo...

Estas metidaso en todo esto de la seguridad haha! pense que era solo un pasatiempo para ti. Soy Kenji, del salón de cachimbos de la de Lima. El semestre pasado lleve un curso de esto que escribes en US pero usabamos Windows Server 2008. Te deseo lo mejor en lo que hagas! un abrazo.

Andrés Pérez-Albela H. dijo...

Gracias Kenji!

Como ya sabrás es mi pasión y que mejor que compartir lo que uno aprende con la gente que está interesada en el tema.

Cualquier cosa que necesites cuenta conmigo, un fuerte abrazo.

Saludos.

Anónimo dijo...

Hola! está buenísimo el artículo... pero tengo un problema a ver si me puedes ayudar.

Tengo 2 OU principales (Sucursales, CasaMatriz) y en ambas tengo usuarios dependiedo de la ubicación en que se encuentren...

Necesito buscar los datos de un usuario sin saber a que OU pertenece, el problema es que no sé como.

Si yo pongo la OU en el ldap_search me aparecen los datos sin problemas, pero quiero autenticar a usuarios de casa matriz y de sucursales al mismo tiempo, no he encontrado como. Te agradecería mucho si puedes ayudarme.

Saludos!

Pablo.

Andrés Pérez-Albela H. dijo...

@Pablo,

Debes crear un bucle que recorra todos los OU's y hacer la consulta por cada uno y que cuando encuentre al usuario haga un break al bucle y salga con el resultado. Para esto debes tenerlos en un arreglo fijo o almacenados en una base de datos.

Espero me entiendas.

Saludos!

Anónimo dijo...

hola:
hice una aplicación de escritorio y no se cuales son los parametros que se ponen a la hora de configurar dicha aplicación con un LDAP, es decir para que mis usuarios se autentiquen en el directorio.

Anónimo dijo...

me ha servido mucho tu codigo muchas gracias, sin embargo en la parte de $resultados=ldap_get_entries($this->ds,$busqueda); por ningun motivo me quiere cargar el arreglo, lo muestro por pantalla y nada, no sabrias que puede ser????

Dany dijo...

Hola una pregunta, esto que hiciste es para un usuario en particular si quiero hacer un formulario y que el usuario coloque su nombre y contraseña como hago para guardar esa informacion y luego usarla para buscar ese usuario:

hice esto:

require_once 'LDAPhp.class.php';

//Datos del Usuario a Autentificar con tu aplicacion en PHP
//La mayoría de veces estos datos no son una constante, sino valores recibidos por $_POST[]
$usuario = $_POST["usuario"];
$password = $_POST["password"];
//Datos de Configuración del Servidor
$servidor="192.168.0.1";
$puerto="389";
//$usuario = $_GET["usuario"];

//Datos de Configuración para la conexión al LDAP (DN == "Distinguished Name")
$cadenaConexionUsuario = "CN=$usuario,OU=Sistemas,OU=Wendys,DC=wencove,DC=com";

Anónimo dijo...

Estimado Andrés,
Muchísimas gracias por este sensacional post. Gracias a él hemos podido terminar un proyecto que nos traía dolores de cabeza.
Un abrazo desde España.

Andrés Pérez-Albela H. dijo...

Dany,

El usuario que a tu parecer es una constante, debe ser una constante debido a que ese usuario no es el que se consulta para autenticar un formulario web. Ese usuario es para conectarse al LDAP y poder recién hacer la consulta. Es como cuando uno abre el SQL Server Management Studio y escribe un usuario 'sa' y la clave para poder luego hacer consultas.

Saludos.

Andrés Pérez-Albela H. dijo...

Anónimo,

El gusto es mío!

Saludos desde Perú

Anónimo dijo...

Ho la buenos dias me gustaria saber si hay que instalar algun modulo para que funcione ldap en el servidor. yo tan solo he activado la extension_ldap de php.ini

Es para un proyecto final gracias.

Marcelo dijo...

Estimado, me parece interesante tu articulo, justo estoy navegando y buscando informacion de LDAP con PHP, pues tenemos problemas para que se autentinquen en mi Intranet, los usuarios que tienen a nivel AD restriccion de acceso por equipo, queria saber si puedes darme luces de como lograrlo, me puedes escribir a mi correo personal anfossi_mla@hotmail.com. Gracias

Daniel dijo...

Buenas, yo tengo mi servidor ldap y mi autenticación con usuarios, ahora mi pregunta es, podre colocar algún cambiar contraseña para modificar las propiedades del OU del ldap para un usuario en particular? o algo como recordar contraseña. Solo pregunto para olvidarme de las tablas sql.

Andres dijo...

Hola Andres.

Ojala me puedas apoyar ya implemente el codigo que nos expusiste solo que me aparece el siguiente error.

Contraseña Incorrecta para el usuario alopez

Espero me puedas apoyar

Otra duda que tengo es sobre las variables $password y $filtro

ojala me puedas apoyar.

Saludos

Unknown dijo...

buenas tardes, el usuario PHP que es el que usas como enlace, no requiere password?, disculpa mi ignorancia, muy bueno este articulo.
roberto.

Unknown dijo...

buenas tardes, excelente articulo, tengo varias consultas espero me puedas ayudar, 1º- el usuario PHP que usas como enlace requiere password?-
2º- como puedo consultar por un grupo administrador?-
desde ya muchisimas gracias

Unknown dijo...

Me funciona a la perfeccion. Muchas gracias por compartir el conocimiento.
Solo una consulta: Es posible que la autenticacion sea por grupo de dominio?, es decir, yo necesito que solo ciertas personas ingresen no cualquiera del dominio. Estos usuarios pertenecen a distintas OU y cada OU tiene distintos permisos, por lo que mover a esos usuarios a una OU especifica no es posible, por eso pense en hacer un grupo donde esten esos usuarios.
Es posible esto que se me ocurre y, lo mas importante, como lo hago?.

Saludos.