Authentification avec Zend_Auth
Mis en ligne le 27 juillet 2009Tags : 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.
