tutos > Authentification avec Zend_Auth


Authentification avec Zend_Auth

Mis en ligne le 27 juillet 2009
Tags : Zend Auth authentification sessions

Nous utiliserons une base de données comme moyen de stockage des données de session.

Préliminaires

Les deux tables qui nous serviront sont :

mysql> describe users;
+-------------+------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------------------+------+-----+---------+----------------+
| id | smallint(6) | NO | PRI | NULL | auto_increment|
| username | varchar(20) | NO | UNI | | |
| password | varchar(32) | NO | | | |
| last_logged | date | YES | | NULL | |
| cookie | varchar(32) | NO | | | |
| ip | varchar(15) | NO | | | |
| name | varchar(50) | NO | | | |
| role | enum('admin','user','guest') | NO | | guest | |
+-------------+------------------------------+------+-----+---------+----------------+
8 rows in set (0.05 sec)

mysql> describe sessions;
+----------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+-------+
| id | varchar(32) | NO | PRI | NULL | |
| name | varchar(32) | NO | MUL | NULL | |
| modified | int(11) unsigned | NO | | NULL | |
| lifetime | int(11) unsigned | NO | | NULL | |
| data | text | YES | | NULL | |
+----------+------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)


Configuration de la session et de la base :

db_config.php

include_once 'Zend/Db/Table/Abstract.php';

$driver = 'Pdo_Mysql';
$host = 'xxx';
$user = 'xxx';
$password = 'xxx';
$database = 'xxx';

$db = Zend_Db::factory($driver, array('host' => $host, 'username' => $user, 'password' => $password, 'dbname' => $database));
Zend_Db_Table_Abstract::setDefaultAdapter($db);


session_config.php

require_once 'Zend/Session/SaveHandler/DbTable.php';

$session_expire = 60 * 30; // half an hour
$session_idle = 60 * 3; // 3 minutes

Zend_Session::setOptions(array(
'use_cookies' => 1
'use_trans_sid' => 0,
'name' => 'sid',
'cookie_path' => '/' ,
'cookie_domain' => '.' . $_SERVER['SERVER_NAME'] ,
'cookie_lifetime' => 0
)
);

Zend_Session::setSaveHandler(new Zend_Session_SaveHandler_DbTable(array(
'name' => 'sessions',
'primary' => 'id',
'modifiedColumn' => 'modified',
'dataColumn' => 'data',
'lifetimeColumn' => 'lifetime' ,
'lifetime' => $session_expire,
'overrideLifetime' => true
)
));


Nous allons interagir avec la table `users`, l'on a donc une classe Users 'bare bones'. L'on pourra y ajouter des méthodes vraiment spécifiques si le besoin s'en fait sentir mais il est inutile de réinventer la roue.

Users.php

class Users extends Zend_Db_Table_Abstract {

protected $_name='users';
protected $_primary = 'id';

}


Le contrôleur d'accès

Il ne s'agit pas là d'un contrôleur tel qu'entendu dans le vocabulaire lié aux MVCs, mais d'une simple classe qui va gérer l'authentification, les entrées et sorties, les cookies etc.

Bouncer.php

include_once ABSLPATH . '/assets/lib/Users.php';
include_once ABSLPATH . '/assets/helpers/cookies.php';
include_once 'Zend/Auth.php';
require_once 'Zend/Auth/Storage/Session.php';

class Bouncer {

protected static $_instance;
protected $_user;
protected $_auth;
protected $_auth_storage;
protected $_state_data;

private function __construct() {
global $db;
global $session_idle;

include_once 'Zend/Auth/Adapter/DbTable.php';
$this->_user = new Zend_Auth_Adapter_DbTable($db, 'users', 'username', 'password');

$this->_auth_storage = new Zend_Auth_Storage_Session();

$auth_session = new Zend_Session_Namespace($this->_auth_storage->getNamespace());
$auth_session->setExpirationSeconds($session_idle);

$this->_auth = Zend_Auth::getInstance();
$this->_auth->setStorage($this->_auth_storage);

$this->_state_data = new Zend_Session_Namespace( 'History');
}

public static function getInstance() {
if (null === self::$_instance)
self::$_instance = new self();
return self::$_instance;
}

private function _redirect() {
if (isset($this->_state_data->page))
redirect($this->_state_data->page);
redirect('/admin/index.php');
}

public function checkLogged() {
if (!$this->_auth->hasIdentity()) {
// si un cookie existe, on réinstaure l'identité
$cookie_data = get_cookie('remember');
if ($cookie_data) {
list($uid, $cookie_id) = unserialize($cookie_data);
if (!$uid or !$cookie_id)
redirect('/admin/login.php');
$user_tbl = new Users;
$select = $user_tbl->select()->where("`id`=?", $uid)->where("`cookie`=?", $cookie_id);
$user_data = $user_tbl->fetchRow($select);

if (is_object($user_data))
$this->_auth_storage->write(array('user' => $user_data->username, 'name' => $user_data->name));
} else
redirect('/admin/login.php');
}
}

public function authenticate($user, $pass) {
$this->_user->setCredentialTreatment("MD5(?) AND role = 'admin'");
$this->_user->setIdentity($user);
$this->_user->setCredential($pass);

$authentication = $this->_auth->authenticate($this->_user);

return $authentication->isValid();
}

public function allowIn($remember) {
$cookie_id = generate_cookie_id();

$user_tbl = new Users;
$user_data = $this->_user->getResultRowObject(array('id', 'username', 'name'));
$this->_auth_storage->write(array('user' => $user_data->username, 'name' => $user_data->name));

// update users table
$update = array(
'cookie' => $cookie_id,
'last_logged' => new Zend_Db_Expr('CURDATE()'),
'ip' => $_SERVER['REMOTE_ADDR']
);
$where = $user_tbl->getAdapter()->quoteInto('id = ?', $user_data->id);
$user_tbl ->update($update, $where);

if ($remember) {
$array = array($user_data->id, $cookie_id);
$cookie_data = serialize($array);
set_cookie('remember', $cookie_data);
}

$this->_redirect();
}

public function kickOut() {
Zend_Session::destroy();
delete_cookie('remember');
}

public function getIdentity() {
return $this->_auth_storage->read();
}

public function getState($key = null) {
if ($key != null && is_string($key))
return $this->_state_data->__get($key);
else
return $this->_state_data->getIterator();
}

public function setState() {
$nargs = func_num_args();
$args = func_get_args();
if ($nargs == 0)
return;
if (is_string($args[0]) && is_scalar($args[1]))
$this->_state_data->__set($args[0], $args[1]);
else if (is_array($args[0]))
foreach($args[0] as $key => $value)
$this->_state_data->__set($key, $value);
}

}


La session comprend deux espaces nommés: Zend_Auth qui stocke l'identité, et History qui peut être utilisé pour stoker des informations concernant les actions de l'utilisateur. Nous nous contenterons ici de sauvegarder la page visitée.
J'assure en outre ma propre gestion des cookies puisque le rememberMe() de Zend/Session ne me paraît ni adéquat ni pratique.

La page de login

login.php

include '../config/config.php';
include_once ABSLPATH . '/config/db_config.php';
include_once ABSLPATH . '/config/session_config.php';
include_once ABSLPATH . '/assets/helpers/template.php';
include_once ABSLPATH . '/assets/helpers/forms.php';
include_once ABSLPATH . '/assets/helpers/functions.php';
include_once ABSLPATH . '/assets/lib/Bouncer.php';

$form = new HTML_QuickForm2('form', 'post', array('name' => 'loginform', 'action' => $_SERVER['REQUEST_URI']), false);
$fieldset = $form->addElement('fieldset')->setLabel('Veuillez vous identifier');
$userName = $fieldset->addElement('text', 'user')->setLabel("Identifiant");
$userName->addRule('required', "Vous devez entrer votre identifiant.")
->and_($userName->addRule('length-5-12', "Votre identifiant doit comporter entre 5 et 12 caractères."));
$userPass = $fieldset->addElement('password', 'pass')->setLabel("Mot de passe");
$userPass->addRule('required', "Vous devez entrer votre mot de passe.")
->and_($userPass->addRule('length-5-12', "Votre mot de passe doit comporter entre 5 et 12 caractères."));
$fieldset->addElement('checkbox', 'remember')->setContent("Se souvenir de moi");
$form->addElement('submit', 'submit', array('value' => 'Se connecter'));

if ($form->validate()) {
process_data($form->getValue());
} else {
begin_html();
get_header(true);
load_stylesheets(array('styles.css'));
render_form($form);
get_footer();
}

function process_data(&$values) {

$remember = (isset($values['remember'])) ? true : false;

if (!authenticate($values['user'], $values['pass'], $remember)))
redirect("admin/login.php);

}

function authenticate($user, $pass, $remember) {

$bouncer = Bouncer::getInstance();
if ($bouncer->authenticate($user, $pass))
$bouncer->allowIn($remember);
else
return LOGIN_ERROR;
}


Je reste ici fidèle à QuickForm2 (de la librairie PEAR) pour ce qui concerne la gestion des formulaires.

La page de logout

logout.php

include '../config/config.php';
include_once ABSLPATH . '/config/db_config.php';
include_once ABSLPATH . '/config/session_config.php';
include_once ABSLPATH . '/assets/helpers/functions.php';
include_once ABSLPATH . '/assets/lib/Bouncer.php';

Bouncer::getInstance()->kickOut();
redirect('/admin/index.php');


Pour s'assurer que la page visitée l'est bien par un utilisateur identifié, la code suivant suffit :

$bouncer = Bouncer::getInstance();
$bouncer->setState('page', $_SERVER['REQUEST_URI']); // facultatif
$bouncer->checkLogged();
...


Voilà. C'est parfaitement fonctionnel.