TigerZF
🌐Español

15.2. Autenticación con tabla de base de datos

15.2.1. Introducción

Zend_Auth_Adapter_DbTable proporciona la capacidad de autenticarse contra credenciales almacenadas en una tabla de base de datos. Debido a que Zend_Auth_Adapter_DbTable requiere que se pase una instancia de Zend_Db_Adapter_Abstract a su constructor, cada instancia está vinculada a una conexión de base de datos en particular. Otras opciones de configuración pueden establecerse mediante el constructor y mediante métodos de instancia, uno para cada opción.

Las opciones de configuración disponibles incluyen:

  • tableName: Este es el nombre de la tabla de base de datos que contiene las credenciales de autenticación, y contra la cual se realiza la consulta de autenticación de la base de datos.

  • identityColumn: Este es el nombre de la columna de la tabla de base de datos utilizada para representar la identidad. La columna de identidad debe contener valores únicos, tales como un nombre de usuario o dirección de correo electrónico.

  • credentialColumn: Este es el nombre de la columna de la tabla de base de datos utilizada para representar la credencial. Bajo un esquema simple de autenticación de identidad y contraseña, el valor de la credencial corresponde a la contraseña. Vea también la opción credentialTreatment.

  • credentialTreatment: En muchos casos, las contraseñas y otros datos sensibles se cifran, aplican hash, codifican, ofuscan, salan u otramente se tratan mediante alguna función o algoritmo. Al especificar una cadena de tratamiento parametrizada con este método, tal como 'MD5(?)' o 'PASSWORD(?)', un desarrollador puede aplicar dicho SQL arbitrario sobre los datos de credencial de entrada. Dado que estas funciones son específicas del RDBMS subyacente, consulte el manual de la base de datos para conocer la disponibilidad de dichas funciones para su sistema de base de datos.

Ejemplo 15.3. Uso básico

Como se explicó en la introducción, el constructor de Zend_Auth_Adapter_DbTable requiere una instancia de Zend_Db_Adapter_Abstract que sirva como la conexión de base de datos a la cual está vinculada la instancia del adaptador de autenticación. Primero, se debe crear la conexión de base de datos.

El siguiente código crea un adaptador para una base de datos en memoria, crea un esquema de tabla simple, e inserta una fila contra la cual podemos realizar una consulta de autenticación posteriormente. Este ejemplo requiere que la extensión PDO SQLite esté disponible:

// Create an in-memory SQLite database connection
$dbAdapter = new Zend_Db_Adapter_Pdo_Sqlite(array('dbname' =>
                                                  ':memory:'));

// Build a simple table creation query
$sqlCreate = 'CREATE TABLE [users] ('
           . '[id] INTEGER  NOT NULL PRIMARY KEY, '
           . '[username] VARCHAR(50) UNIQUE NOT NULL, '
           . '[password] VARCHAR(32) NULL, '
           . '[real_name] VARCHAR(150) NULL)';

// Create the authentication credentials table
$dbAdapter->query($sqlCreate);

// Build a query to insert a row for which authentication may succeed
$sqlInsert = "INSERT INTO users (username, password, real_name) "
           . "VALUES ('my_username', 'my_password', 'My Real Name')";

// Insert the data
$dbAdapter->query($sqlInsert);

Con la conexión de base de datos y los datos de la tabla disponibles, se puede crear una instancia de Zend_Auth_Adapter_DbTable. Los valores de las opciones de configuración pueden pasarse al constructor o diferirse como parámetros a métodos setter después de la instanciación:

// Configure the instance with constructor parameters...
$authAdapter = new Zend_Auth_Adapter_DbTable(
    $dbAdapter,
    'users',
    'username',
    'password'
);

// ...or configure the instance with setter methods
$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);

$authAdapter
    ->setTableName('users')
    ->setIdentityColumn('username')
    ->setCredentialColumn('password')
;

En este punto, la instancia del adaptador de autenticación está lista para aceptar consultas de autenticación. Para formular una consulta de autenticación, los valores de credencial de entrada se pasan al adaptador antes de llamar al método authenticate():

// Set the input credential values (e.g., from a login form)
$authAdapter
    ->setIdentity('my_username')
    ->setCredential('my_password')
;

// Perform the authentication query, saving the result

Además de la disponibilidad del método getIdentity() en el objeto de resultado de autenticación, Zend_Auth_Adapter_DbTable también admite la recuperación de la fila de la tabla al lograr la autenticación:

// Print the identity
echo $result->getIdentity() . "\n\n";

// Print the result row
print_r($authAdapter->getResultRowObject());

/* Output:
my_username

Array
(
    [id] => 1
    [username] => my_username
    [password] => my_password
    [real_name] => My Real Name
)

Dado que la fila de la tabla contiene el valor de la credencial, es importante asegurar los valores contra el acceso no deseado.


15.2.2. Uso avanzado: persistiendo un objeto de resultado DbTable

Por defecto, Zend_Auth_Adapter_DbTable devuelve al objeto auth la identidad proporcionada tras una autenticación exitosa. Otro caso de uso, en el que los desarrolladores desean almacenar en el mecanismo de almacenamiento persistente de Zend_Auth un objeto de identidad que contenga otra información útil, se resuelve usando el método getResultRowObject() para devolver un objeto stdClass. El siguiente fragmento de código ilustra su uso:

// authenticate with Zend_Auth_Adapter_DbTable
$result = $this->_auth->authenticate($adapter);

if ($result->isValid()) {
    // store the identity as an object where only the username and
    // real_name have been returned
    $storage = $this->_auth->getStorage();
    $storage->write($adapter->getResultRowObject(array(
        'username',
        'real_name',
    )));

    // store the identity as an object where the password column has
    // been omitted
    $storage->write($adapter->getResultRowObject(
        null,
        'password'
    ));

    /* ... */

} else {

    /* ... */

}

15.2.3. Uso avanzado mediante un ejemplo

Si bien el propósito principal de Zend_Auth (y por consiguiente de Zend_Auth_Adapter_DbTable) es principalmente la autenticación y no la autorización, hay algunas instancias y problemas que rozan la línea entre a qué dominio pertenecen. Dependiendo de cómo haya decidido explicar su problema, a veces tiene sentido resolver lo que podría parecer un problema de autorización dentro del adaptador de autenticación.

Con esa aclaración fuera del camino, Zend_Auth_Adapter_DbTable tiene algunos mecanismos incorporados que pueden aprovecharse para verificaciones adicionales en el momento de la autenticación para resolver algunos problemas comunes de los usuarios.

// The status field value of an account is not equal to "compromised"
$adapter = new Zend_Auth_Adapter_DbTable(
    $db,
    'users',
    'username',
    'password',
    'MD5(?) AND status != "compromised"'
);

// The active field value of an account is equal to "TRUE"
$adapter = new Zend_Auth_Adapter_DbTable(
    $db,
    'users',
    'username',
    'password',
    'MD5(?) AND active = "TRUE"'

Otro escenario puede ser la implementación de un mecanismo de "salting" (salado). El salado es un término que se refiere a una técnica que puede mejorar en gran medida la seguridad de su aplicación. Se basa en la idea de que concatenar una cadena aleatoria a cada contraseña hace que sea imposible lograr un ataque de fuerza bruta exitoso en la base de datos usando valores de hash precalculados de un diccionario.

Por lo tanto, necesitamos modificar nuestra tabla para almacenar nuestra cadena de sal:

$sqlAlter = "ALTER TABLE [users] "
          . "ADD COLUMN [password_salt] "
          . "AFTER [password]";

Aquí hay una forma sencilla de generar una cadena de sal para cada usuario en el registro:

for ($i = 0; $i < 50; $i++) {
    $dynamicSalt .= chr(rand(33, 126));

Y ahora construyamos el adaptador:

$adapter = new Zend_Auth_Adapter_DbTable(
    $db,
    'users',
    'username',
    'password',
    "MD5(CONCAT('"
    . Zend_Registry::get('staticSalt')
    . "', ?, password_salt))"
);
[Note] Nota

Puede mejorar aún más la seguridad usando un valor de sal estático codificado directamente en su aplicación. En caso de que su base de datos se vea comprometida (por ejemplo, mediante un ataque de inyección SQL) pero su servidor web permanezca intacto, sus datos seguirán siendo inutilizables para el atacante.

Otra alternativa es usar el método getDbSelect() de Zend_Auth_Adapter_DbTable después de que se haya construido el adaptador. Este método devolverá la instancia del objeto Zend_Db_Select que se utilizará para completar la rutina authenticate(). Es importante notar que este método siempre devolverá el mismo objeto sin importar si se ha llamado o no a authenticate(). Este objeto no tendrá ninguna de la información de identidad o credencial en él, ya que esos valores se colocan en el objeto select en el momento de authenticate().

Un ejemplo de una situación en la que se podría querer usar el método getDbSelect() sería verificar el estado de un usuario, en otras palabras, ver si la cuenta de ese usuario está habilitada.

// Continuing with the example from above
$adapter = new Zend_Auth_Adapter_DbTable(
    $db,
    'users',
    'username',
    'password',
    'MD5(?)'
);

// get select object (by reference)
$select = $adapter->getDbSelect();
$select->where('active = "TRUE"');

// authenticate, this ensures that users.active = TRUE
$adapter->authenticate();