TigerZF
🌐Español

15.5. Autenticación LDAP

15.5.1. Introducción

Zend_Auth_Adapter_Ldap permite la autenticación de aplicaciones web con servicios LDAP. Sus características incluyen la canonicalización de nombres de usuario y de dominio, autenticación multi-dominio y capacidades de conmutación por error (failover). Ha sido probado con Microsoft Active Directory y OpenLDAP, pero también debería funcionar con otros proveedores de servicios LDAP.

Esta documentación incluye una guía para usar Zend_Auth_Adapter_Ldap, una exploración de su API, un esquema de las diversas opciones disponibles, información de diagnóstico para solucionar problemas de autenticación, y opciones de ejemplo tanto para servidores Active Directory como OpenLDAP.

15.5.2. Uso

Para incorporar rápidamente la autenticación con Zend_Auth_Adapter_Ldap en su aplicación, incluso si no está usando Zend_Controller, la parte esencial de su código debería tener un aspecto similar al siguiente:

$username = $this->_request->getParam('username');
$password = $this->_request->getParam('password');

$auth = Zend_Auth::getInstance();

$config = new Zend_Config_Ini('../application/config/config.ini',
                              'production');
$log_path = $config->ldap->log_path;
$options = $config->ldap->toArray();
unset($options['log_path']);

$adapter = new Zend_Auth_Adapter_Ldap($options, $username,
                                      $password);

$result = $auth->authenticate($adapter);

if ($log_path) {
    $messages = $result->getMessages();

    $logger = new Zend_Log();
    $logger->addWriter(new Zend_Log_Writer_Stream($log_path));
    $filter = new Zend_Log_Filter_Priority(Zend_Log::DEBUG);
    $logger->addFilter($filter);

    foreach ($messages as $i => $message) {
        if ($i-- > 1) { // $messages[2] and up are log messages
            $message = str_replace("\n", "\n  ", $message);
            $logger->log("Ldap: $i: $message", Zend_Log::DEBUG);
        }
    }
}

Por supuesto, el código de registro (logging) es opcional, pero es muy recomendable usar un registrador (logger). Zend_Auth_Adapter_Ldap registrará prácticamente toda la información que cualquiera pudiera desear en $messages (más abajo), lo cual es una característica interesante en sí misma para algo que tiene fama de ser notoriamente difícil de depurar.

El código de Zend_Config_Ini se utiliza arriba para cargar las opciones del adaptador. También es opcional. Un array normal funcionaría igual de bien. A continuación se muestra un archivo de ejemplo application/config/config.ini que tiene opciones para dos servidores separados. Con varios conjuntos de opciones de servidor, el adaptador intentará cada uno, en orden, hasta que las credenciales se autentiquen con éxito. Los nombres de los servidores (por ejemplo, 'server1' y 'server2') son en gran medida arbitrarios. Para más detalles sobre el array de opciones, consulte la sección Opciones del servidor más abajo. Tenga en cuenta que Zend_Config_Ini requiere que cualquier valor con caracteres "igual" (=) deba estar entrecomillado (como los DN mostrados abajo).

[production]

ldap.log_path = /tmp/ldap.log

; Typical options for OpenLDAP
ldap.server1.host = s0.foo.net
ldap.server1.accountDomainName = foo.net
ldap.server1.accountDomainNameShort = FOO
ldap.server1.accountCanonicalForm = 3
ldap.server1.username = "CN=user1,DC=foo,DC=net"
ldap.server1.password = pass1
ldap.server1.baseDn = "OU=Sales,DC=foo,DC=net"
ldap.server1.bindRequiresDn = true

; Typical options for Active Directory
ldap.server2.host = dc1.w.net
ldap.server2.useStartTls = true
ldap.server2.accountDomainName = w.net
ldap.server2.accountDomainNameShort = W
ldap.server2.accountCanonicalForm = 3
ldap.server2.baseDn = "CN=Users,DC=w,DC=net"

La configuración anterior indicará a Zend_Auth_Adapter_Ldap que intente autenticar a los usuarios primero con el servidor OpenLDAP s0.foo.net. Si la autenticación falla por cualquier motivo, se intentará con el servidor AD dc1.w.net.

Con servidores en dominios diferentes, esta configuración ilustra la autenticación multi-dominio. También puede tener varios servidores en el mismo dominio para proporcionar redundancia.

Tenga en cuenta que en este caso, aunque OpenLDAP no necesita el nombre de dominio corto estilo NetBIOS utilizado por Windows, lo proporcionamos aquí con fines de canonicalización de nombres (descrito en la sección Canonicalización de nombres de usuario más abajo).

15.5.3. La API

El constructor de Zend_Auth_Adapter_Ldap acepta tres parámetros.

El parámetro $options es obligatorio y debe ser un array que contenga uno o más conjuntos de opciones. Tenga en cuenta que es un array de arrays de opciones de Zend_Ldap. Incluso si va a usar un único servidor LDAP, las opciones deben seguir estando dentro de otro array.

A continuación se muestra la salida de print_r() de un ejemplo de parámetro de opciones que contiene dos conjuntos de opciones de servidor para servidores LDAP s0.foo.net y dc1.w.net (las mismas opciones que la representación INI anterior):

Array
(
    [server2] => Array
        (
            [host] => dc1.w.net
            [useStartTls] => 1
            [accountDomainName] => w.net
            [accountDomainNameShort] => W
            [accountCanonicalForm] => 3
            [baseDn] => CN=Users,DC=w,DC=net
        )

    [server1] => Array
        (
            [host] => s0.foo.net
            [accountDomainName] => foo.net
            [accountDomainNameShort] => FOO
            [accountCanonicalForm] => 3
            [username] => CN=user1,DC=foo,DC=net
            [password] => pass1
            [baseDn] => OU=Sales,DC=foo,DC=net
            [bindRequiresDn] => 1
        )

)

La información proporcionada en cada conjunto de opciones anterior es diferente principalmente porque AD no requiere que un nombre de usuario esté en forma de DN al enlazar (bind) (consulte la opción bindRequiresDn en la sección Opciones del servidor más abajo), lo que significa que podemos omitir varias opciones asociadas con la obtención del DN de un nombre de usuario que se está autenticando.

[Note] ¿Qué es un Distinguished Name?

Un DN o "nombre distintivo" (distinguished name) es una cadena que representa la ruta a un objeto dentro del directorio LDAP. Cada componente separado por comas es un atributo y valor que representa un nodo. Los componentes se evalúan en orden inverso. Por ejemplo, la cuenta de usuario CN=Bob Carter,CN=Users,DC=w,DC=net se encuentra directamente dentro del contenedor CN=Users,DC=w,DC=net. Esta estructura se explora mejor con un navegador LDAP como el complemento (snap-in) MMC de ADSI Edit para Active Directory o phpLDAPadmin.

Los nombres de los servidores (por ejemplo, 'server1' y 'server2' mostrados arriba) son en gran medida arbitrarios, pero por el bien del uso de Zend_Config, los identificadores deben estar presentes (en lugar de ser índices numéricos) y no deben contener ningún carácter especial usado por los formatos de archivo asociados (por ejemplo, el separador de propiedades '.' de INI, '&' para las referencias de entidad XML, etc).

Con varios conjuntos de opciones de servidor, el adaptador puede autenticar usuarios en múltiples dominios y proporcionar conmutación por error (failover) de forma que si un servidor no está disponible, se consulte otro.

[Note] Los detalles sórdidos: ¿qué ocurre en el método authenticate?

Cuando se llama al método authenticate(), el adaptador itera sobre cada conjunto de opciones de servidor, las establece en la instancia interna de Zend_Ldap, y llama al método Zend_Ldap::bind() con el nombre de usuario y la contraseña que se están autenticando. La clase Zend_Ldap comprueba si el nombre de usuario está cualificado con un dominio (por ejemplo, tiene un componente de dominio como alice@foo.net o FOO\alice). Si hay un dominio presente, pero no coincide con ninguno de los nombres de dominio del servidor (foo.net o FOO), se lanza una excepción especial que es capturada por Zend_Auth_Adapter_Ldap, la cual hace que ese servidor sea ignorado y se seleccione el siguiente conjunto de opciones de servidor. Si un dominio coincide, o si el usuario no proporcionó un nombre de usuario cualificado, Zend_Ldap procede a intentar enlazar (bind) con las credenciales proporcionadas. Si el enlace no tiene éxito, Zend_Ldap lanza una Zend_Ldap_Exception que es capturada por Zend_Auth_Adapter_Ldap y se prueba el siguiente conjunto de opciones de servidor. Si el enlace tiene éxito, la iteración se detiene, y el método authenticate() del adaptador devuelve un resultado exitoso. Si se han probado todos los conjuntos de opciones de servidor sin éxito, la autenticación falla, y authenticate() devuelve un resultado fallido con mensajes de error de la última iteración.

Los parámetros de nombre de usuario y contraseña del constructor de Zend_Auth_Adapter_Ldap representan las credenciales que se están autenticando (es decir, las credenciales proporcionadas por el usuario a través de su formulario de inicio de sesión HTML). Alternativamente, también se pueden establecer con los métodos setUsername() y setPassword().

15.5.4. Opciones del servidor

Cada conjunto de opciones de servidor en el contexto de Zend_Auth_Adapter_Ldap consta de las siguientes opciones, que se pasan, en gran medida sin modificar, a Zend_Ldap::setOptions():

Tabla 15.2. Opciones del servidor

Nombre Descripción
host El nombre de host del servidor LDAP que representan estas opciones. Esta opción es obligatoria.
port El puerto en el que escucha el servidor LDAP. Si useSsl es TRUE, el valor por defecto de port es 636. Si useSsl es FALSE, el valor por defecto de port es 389.
useStartTls Indica si el cliente LDAP debe usar transporte cifrado TLS (también conocido como SSLv2). Un valor de TRUE es muy recomendable en entornos de producción para evitar que las contraseñas se transmitan en texto claro. El valor por defecto es FALSE, ya que los servidores frecuentemente requieren que se instale un certificado por separado después de la instalación. Las opciones useSsl y useStartTls son mutuamente excluyentes. Se debería favorecer la opción useStartTls sobre useSsl, pero no todos los servidores soportan este mecanismo más reciente.
useSsl Indica si el cliente LDAP debe usar transporte cifrado SSL. Las opciones useSsl y useStartTls son mutuamente excluyentes, pero se debería favorecer useStartTls si el servidor y la biblioteca cliente LDAP lo soportan. Este valor también cambia el valor por defecto de port (consulte la descripción de port arriba).
username El DN de la cuenta usada para realizar búsquedas de DN de cuentas. Los servidores LDAP que requieren que el nombre de usuario esté en forma de DN al realizar el "bind" requieren esta opción. Es decir, si bindRequiresDn es TRUE, esta opción es obligatoria. Esta cuenta no necesita ser una cuenta con privilegios; una cuenta con acceso de solo lectura a los objetos bajo baseDn es todo lo necesario (y lo preferible según el Principio del Mínimo Privilegio).
password La contraseña de la cuenta usada para realizar búsquedas de DN de cuentas. Si esta opción no se proporciona, el cliente LDAP intentará un "bind anónimo" al realizar las búsquedas de DN de cuentas.
bindRequiresDn Algunos servidores LDAP requieren que el nombre de usuario usado para el bind esté en forma de DN como CN=Alice Baker,OU=Sales,DC=foo,DC=net (básicamente todos los servidores excepto AD). Si esta opción es TRUE, esto indica a Zend_Ldap que recupere automáticamente el DN correspondiente al nombre de usuario que se está autenticando, si aún no está en forma de DN, y luego vuelva a enlazar (bind) con el DN correcto. El valor por defecto es FALSE. Actualmente solo se sabe que Microsoft Active Directory Server (ADS) no requiere que los nombres de usuario estén en forma de DN al hacer bind, por lo que esta opción puede ser FALSE con AD (y debería serlo, ya que recuperar el DN requiere un viaje de ida y vuelta adicional al servidor). En caso contrario, esta opción debe establecerse en TRUE (por ejemplo, para OpenLDAP). Esta opción también controla el valor por defecto de acountFilterFormat usado al buscar cuentas. Consulte la opción accountFilterFormat.
baseDn El DN bajo el cual se ubican todas las cuentas que se autentican. Esta opción es obligatoria. Si no está seguro sobre el valor correcto de baseDn, debería ser suficiente derivarlo del dominio DNS del usuario usando componentes DC=. Por ejemplo, si el nombre principal del usuario es alice@foo.net, un baseDn de DC=foo,DC=net debería funcionar. No obstante, una ubicación más precisa (por ejemplo, OU=Sales,DC=foo,DC=net) será más eficiente.
accountCanonicalForm Un valor de 2, 3 o 4 que indica la forma a la que deben canonicalizarse los nombres de cuenta tras una autenticación exitosa. Los valores son los siguientes: 2 para nombres estilo nombre de usuario tradicional (por ejemplo, alice), 3 para nombres estilo barra invertida (por ejemplo, FOO\alice) o 4 para nombres de usuario estilo principal (por ejemplo, alice@foo.net). El valor por defecto es 4 (por ejemplo, alice@foo.net). Por ejemplo, con un valor de 3, la identidad devuelta por Zend_Auth_Result::getIdentity() (y Zend_Auth::getIdentity(), si se usó Zend_Auth) será siempre FOO\alice, independientemente de la forma que Alice haya proporcionado, ya sea alice, alice@foo.net, FOO\alice, FoO\aLicE, foo.net\alice, etc. Consulte la sección Canonicalización de nombres de cuenta en la documentación de Zend_Ldap para más detalles. Tenga en cuenta que al usar varios conjuntos de opciones de servidor se recomienda, aunque no es obligatorio, que se use el mismo accountCanonicalForm con todas las opciones de servidor, de modo que los nombres de usuario resultantes se canonicalicen siempre a la misma forma (por ejemplo, si canonicaliza a EXAMPLE\username con un servidor AD pero a username@example.com con un servidor OpenLDAP, eso puede resultar incómodo para la lógica de alto nivel de la aplicación).
accountDomainName El nombre de dominio FQDN para el cual el servidor LDAP de destino es una autoridad (por ejemplo, example.com). Esta opción se usa para canonicalizar nombres de forma que el nombre de usuario proporcionado por el usuario pueda convertirse según sea necesario para el bind. También se usa para determinar si el servidor es una autoridad para el nombre de usuario proporcionado (por ejemplo, si accountDomainName es foo.net y el usuario proporciona bob@bar.net, no se consultará el servidor y se producirá un fallo). Esta opción no es obligatoria, pero si no se proporciona, no se admiten los nombres de usuario en forma de nombre principal (por ejemplo, alice@foo.net). Se recomienda encarecidamente proporcionar esta opción, ya que hay muchos casos de uso que requieren generar la forma de nombre principal.
accountDomainNameShort El dominio 'corto' para el cual el servidor LDAP de destino es una autoridad (por ejemplo, FOO). Tenga en cuenta que existe una correspondencia 1:1 entre accountDomainName y accountDomainNameShort. Esta opción debería usarse para especificar el nombre de dominio NetBIOS de redes Windows, pero también puede ser usada por servidores no AD (por ejemplo, por consistencia cuando hay varios conjuntos de opciones de servidor con el accountCanonicalForm estilo barra invertida). Esta opción no es obligatoria, pero si no se proporciona, no se admiten los nombres de usuario en forma de barra invertida (por ejemplo, FOO\alice).
accountFilterFormat El filtro de búsqueda LDAP usado para buscar cuentas. Esta cadena es una expresión estilo printf() que debe contener un '%s' para acomodar el nombre de usuario. El valor por defecto es '(&(objectClass=user)(sAMAccountName=%s))', a menos que bindRequiresDn esté establecido en TRUE, en cuyo caso el valor por defecto es '(&(objectClass=posixAccount)(uid=%s))'. Por ejemplo, si por alguna razón quisiera usar bindRequiresDn = true con AD, necesitaría establecer accountFilterFormat = '(&(objectClass=user)(sAMAccountName=%s))'.
optReferrals Si se establece en TRUE, esta opción indica al cliente LDAP que se deben seguir las referencias (referrals). El valor por defecto es FALSE.

[Note] Nota

Si habilita useStartTls = TRUE o useSsl = TRUE, puede que descubra que el cliente LDAP genera un error indicando que no puede validar el certificado del servidor. Suponiendo que la extensión LDAP de PHP esté finalmente enlazada con las bibliotecas de cliente de OpenLDAP, para resolver este problema puede establecer "TLS_REQCERT never" en el archivo ldap.conf del cliente OpenLDAP (y reiniciar el servidor web) para indicar al cliente OpenLDAP que confía en el servidor. Alternativamente, si le preocupa que el servidor pueda ser suplantado, puede exportar el certificado raíz del servidor LDAP y colocarlo en el servidor web para que el cliente OpenLDAP pueda validar la identidad del servidor.

15.5.5. Recopilación de mensajes de depuración

Zend_Auth_Adapter_Ldap recopila información de depuración dentro de su método authenticate(). Esta información se almacena en el objeto Zend_Auth_Result como mensajes. El array devuelto por Zend_Auth_Result::getMessages() se describe a continuación

Tabla 15.3. Mensajes de depuración

Índice del array de mensajes Descripción
Índice 0 Un mensaje genérico y amigable para el usuario, adecuado para mostrarse a los usuarios (por ejemplo, "Credenciales no válidas"). Si la autenticación tiene éxito, esta cadena está vacía.
Índice 1 Un mensaje de error más detallado que no es adecuado para mostrarse a los usuarios pero que debería registrarse en beneficio de los operadores del servidor. Si la autenticación tiene éxito, esta cadena está vacía.
Índices 2 y superiores Todos los mensajes de registro en orden, empezando por el índice 2.

En la práctica, el índice 0 debería mostrarse al usuario (por ejemplo, usando el ayudante FlashMessenger), el índice 1 debería registrarse y, si se está recopilando información de depuración, los índices 2 y superiores también podrían registrarse (aunque el mensaje final siempre incluye la cadena del índice 1).

15.5.6. Opciones comunes para servidores específicos

15.5.6.1. Opciones para Active Directory

Para ADS, las siguientes opciones son destacables:

Tabla 15.4. Opciones para Active Directory

Nombre Notas adicionales
host Como con todos los servidores, esta opción es obligatoria.
useStartTls Por motivos de seguridad, esto debería ser TRUE si el servidor tiene instalado el certificado necesario.
useSsl Posiblemente se use como alternativa a useStartTls (ver arriba).
baseDn Como con todos los servidores, esta opción es obligatoria. Por defecto AD coloca todas las cuentas de usuario bajo el contenedor Users (por ejemplo, CN=Users,DC=foo,DC=net), pero el valor por defecto no es habitual en organizaciones grandes. Pregunte a su administrador de AD cuál sería el mejor DN para las cuentas de su aplicación.
accountCanonicalForm Casi con toda seguridad querrá que esto sea 3 para nombres estilo barra invertida (por ejemplo, FOO\alice), que son los más familiares para los usuarios de Windows. No debería usar la forma no cualificada 2 (por ejemplo, alice), ya que esto puede conceder acceso a su aplicación a usuarios con el mismo nombre de usuario en otros dominios de confianza (por ejemplo, BAR\alice y FOO\alice se tratarán como el mismo usuario). (Vea también la nota más abajo.)
accountDomainName Esto es obligatorio con AD a menos que se use accountCanonicalForm 2, lo cual, de nuevo, se desaconseja.
accountDomainNameShort El nombre NetBIOS del dominio en el que están los usuarios y para el cual el servidor AD es una autoridad. Esto es obligatorio si se usa el accountCanonicalForm estilo barra invertida.

[Note] Nota

Técnicamente no debería haber peligro de autenticación cruzada accidental entre dominios con la implementación actual de Zend_Auth_Adapter_Ldap, ya que los dominios del servidor se comprueban explícitamente, pero esto puede no ser cierto en una implementación futura que descubra el dominio en tiempo de ejecución, o si se usa un adaptador alternativo (por ejemplo, Kerberos). En general, se sabe que la ambigüedad en el nombre de la cuenta es el origen de problemas de seguridad, así que trate siempre de usar nombres de cuenta cualificados.

15.5.6.2. Opciones para OpenLDAP

Para OpenLDAP o un servidor LDAP genérico que use un esquema típico estilo posixAccount, las siguientes opciones son destacables:

Tabla 15.5. Opciones para OpenLDAP

Nombre Notas adicionales
host Como con todos los servidores, esta opción es obligatoria.
useStartTls Por motivos de seguridad, esto debería ser TRUE si el servidor tiene instalado el certificado necesario.
useSsl Posiblemente se use como alternativa a useStartTls (ver arriba).
username Obligatorio y debe ser un DN, ya que OpenLDAP requiere que los nombres de usuario estén en forma de DN al realizar un bind. Intente usar una cuenta sin privilegios.
password La contraseña correspondiente al nombre de usuario anterior, pero puede omitirse si el servidor LDAP permite un bind anónimo para consultar cuentas de usuario.
bindRequiresDn Obligatorio y debe ser TRUE, ya que OpenLDAP requiere que los nombres de usuario estén en forma de DN al realizar un bind.
baseDn Como con todos los servidores, esta opción es obligatoria e indica el DN bajo el cual se ubican todas las cuentas que se autentican.
accountCanonicalForm Opcional, pero el valor por defecto es 4 (nombres estilo principal como alice@foo.net), lo cual puede no ser ideal si sus usuarios están acostumbrados a nombres estilo barra invertida (por ejemplo, FOO\alice). Para nombres estilo barra invertida use el valor 3.
accountDomainName Obligatorio a menos que esté usando accountCanonicalForm 2, lo cual no se recomienda.
accountDomainNameShort Si AD tampoco se está usando, este valor no es obligatorio. De lo contrario, si se usa accountCanonicalForm 3, esta opción es obligatoria y debería ser un nombre corto que se corresponda adecuadamente con accountDomainName (por ejemplo, si su accountDomainName es foo.net, un buen valor de accountDomainNameShort podría ser FOO).