PHP Conference Japan 2024

La classe SessionHandler

(PHP 5 >= 5.4.0, PHP 7, PHP 8)

Introduction

La classe SessionHandler est une classe spéciale qui peut être utilisée pour exposer le gestionnaire de sauvegarde de session courant en interne de PHP, par héritage. Il y a sept méthodes qui représentent les sept fonctions de rappel du gestionnaire de sauvegarde de session interne (open, close, read, write, destroy, gc et create_sid). Par défaut, cette classe va représenter ce qui est défini dans le gestionnaire de sauvegarde interne, tel que défini par la directive de configuration session.save_handler, qui vaut habituellement files par défaut. D'autres gestionnaires de sauvegarde de session interne sont fournis par des extensions PHP, comme SQLite (avec le paramètre sqlite), Memcache (avec le paramètre memcache), et Memcached (avec le paramètre memcached).

Lorsqu'une instance complète de SessionHandler est définie comme gestionnaire de sauvegarde en utilisant session_set_save_handler(), elle remplacera le gestionnaire de sauvegarde courant. Une classe étendue depuis la classe SessionHandler vous permet d'écraser les méthodes, de les intercepter, ou de les filtrer en appelant la méthode de la classe parent qui remplace en dernier lieu le gestionnaire de session interne de PHP.

Ceci vous permet, par exemple, d'interception les méthodes read et write pour crypter/décrypter les données de session, et de passer le résultat à la classe parente. Alternativement, vous pouvez aussi choisir d'écraser totalement une méthode comme la fonction de rappel de collection des gabarits gc.

En raison du fait que la classe SessionHandler se comporte comme les méthodes du gestionnaire courant de sauvegarde de sessions interne, l'exemple ci-dessus de cryptage peut être appliqué à n'importe quel gestionnaire de sauvegarde sans avoir besoin de connaître les gestionnaires internes.

Pour utiliser cette classe, vous devez commencer par définir un gestionnaire de sauvegarde que vous souhaitez exposer en utilisant session.save_handler puis, passer une instance de la classe SessionHandler ou une étendue à la fonction session_set_save_handler().

Veuillez noter que les méthodes de rappel de cette classe sont destinées à être appelées en interne par PHP, et ne sont pas prévues pour être appelées depuis le code de l'espace utilisateur. Les valeurs retournées seront utilisées de la même façon en interne par PHP. Pour plus d'informations sur le mécanisme des sessions, référez-vous à la documentation sur la fonction session_set_save_handler().

Synopsis de la classe

class SessionHandler implements SessionHandlerInterface, SessionIdInterface {
/* Méthodes */
public close(): bool
public create_sid(): string
public destroy(string $id): bool
public gc(int $max_lifetime): int|false
public open(string $path, string $name): bool
public read(string $id): string|false
public write(string $id, string $data): bool
}

Notes

Avertissement

Cette classe a pour but d'exposer le gestionnaire de sauvegarde de session interne de PHP ; si vous souhaitez écrire votre gestionnaire de sauvegarde personnalisé, vous devez implémenter l'interface SessionHandlerInterface au lieu d'étendre la classe SessionHandler.

Exemples

Exemple #1 Utilisation de la classe SessionHandler pour ajouter un cryptage aux gestionnaires de sauvegarde interne de PHP.

<?php

/**
* Déchiffrement AES 256
*
* @param data $edata
* @param string $password
* @return decrypted data
*/
function decrypt($edata, $password) {
$data = base64_decode($edata);
$salt = substr($data, 0, 16);
$ct = substr($data, 16);

$rounds = 3; // depends on key length
$data00 = $password.$salt;
$hash = array();
$hash[0] = hash('sha256', $data00, true);
$result = $hash[0];
for (
$i = 1; $i < $rounds; $i++) {
$hash[$i] = hash('sha256', $hash[$i - 1].$data00, true);
$result .= $hash[$i];
}
$key = substr($result, 0, 32);
$iv = substr($result, 32,16);

return
openssl_decrypt($ct, 'AES-256-CBC', $key, true, $iv);
}

/**
* Chiffrement AES 256
*
* @param data $data
* @param string $password
* @return base64 encrypted data
*/
function encrypt($data, $password) {
// Générer un sel aléatoire cryptographiquement sécurisé en utilisant random_bytes()
$salt = random_bytes(16);

$salted = '';
$dx = '';
// Salt the key(32) and iv(16) = 48
while (strlen($salted) < 48) {
$dx = hash('sha256', $dx.$password.$salt, true);
$salted .= $dx;
}

$key = substr($salted, 0, 32);
$iv = substr($salted, 32,16);

$encrypted_data = openssl_encrypt($data, 'AES-256-CBC', $key, true, $iv);
return
base64_encode($salt . $encrypted_data);
}

class
EncryptedSessionHandler extends SessionHandler
{
private
$key;

public function
__construct($key)
{
$this->key = $key;
}

public function
read($id)
{
$data = parent::read($id);

if (!
$data) {
return
"";
} else {
return
decrypt($data, $this->key);
}
}

public function
write($id, $data)
{
$data = encrypt($data, $this->key);

return
parent::write($id, $data);
}
}

// Nous interceptons le gestionnaire 'files' natif, mais ceci
// fonctionnera de la même façon avec les autres gestionnaires internes
// comme 'sqlite', 'memcache' ou 'memcached'
// qui sont fournis via des extensions PHP.
ini_set('session.save_handler', 'files');

$key = 'secret_string';
$handler = new EncryptedSessionHandler($key);
session_set_save_handler($handler, true);
session_start();

// processus pour définir et récupérer des valeurs avec la clé, depuis $_SESSION

Note:

Vu que les méthodes de cette classe sont prévues pour être appelées en interne par PHP car faisant parties du mécanisme normal des sessions, les classes enfants appèlent les méthodes parents (i.e. le gestionnaire natif interne actuellement) et doivent retourner false tant que la session n'a pas encore démarrée (soit automatiquement, ou explicitement via la fonction session_start()). Ceci est très important à considérer lors de l'écriture de vos tests unitaires où les méthodes de la classe peuvent être invoquées manuellement.

Sommaire

add a note

User Contributed Notes 8 notes

up
44
rasmus at mindplay dot dk
9 years ago
As the life-cycle of a session handler is fairly complex, I found it difficult to understand when explained using just words - so I traced the function calls made to a custom SessionHandler, and created this overview of precisely what happens when you call various session methods:

https://gist.github.com/mindplay-dk/623bdd50c1b4c0553cd3

I hope this makes it considerably easier to implement a custom SessionHandler and get it right the first time :-)
up
9
tuncdan dot ozdemir dot peng at gmail dot com
1 year ago
Those who are planning to implement your own SessionHandler, let's say using a Database system, please make sure your 'create_sid' method creates a new record in your database with empty string '' as your 'data' using the new session ID created, otherwise when 'read' method is called (which is always called no matter if it is a brand new session or not), you will get an error because there is no record with that session ID yet. The funny part is that the error you get will sound like PHP is trying open the session on your local drive (coming from your .ini file).
up
6
tony at marston-home dot demon dot co dot uk
6 years ago
Your custom session handler should not contain calls to any of the session functions, such as session_name() or session_id(), as the relevant values are passed as arguments on various handler methods. Attempting to obtain values from alternative sources may not work as expected.
up
4
tuncdan dot ozdemir dot peng at gmail dot com
1 year ago
The best way to set up your own session handler is to extend the native SessionHandler (override the 7 methods + constructor as per your need, keeping the same signatures). Optionally, you can also implement SessionUpdateTimestampHandlerInterface if you plan to use 'lazy_write':

Option 1:

class MyOwnSessionHandler extends \SessionHandler { ..... }

Option 2:

class MyOwnSessionHandler extends \SessionHandler implements \SessionUpdateTimestampHandlerInterface { ..... }

I would NOT recommend to do this:

class MyOwnSessionHandler implements \SessionHandlerInterface, \SessionIdInterface, \SessionUpdateTimestampHandlerInterface { ... }

If you are curious, here are the methods called in the order (using PHP 8.2 with XAMPP v3.3.0 on Windows 11 64-bit):

- open (always called)

- validateId and/or create_sid:

validateId is called if you implement SessionUpdateTimestampHandlerInterface. If validation fails, create_sid is called.

create_sid is called if a new session ID is needed: new session, etc.

- read (always called)

- write OR updateTimestamp OR destroy:

if you call 'destroy', neither 'write' or 'updateTimestamp' is called,

if you have the 'lazy_write' on AND implemented SessionUpdateTimestampHandlerInterface,
then 'updateTimestamp' is called instead of 'write' if nothing changes.

- close (always called)
up
2
saccani dot francesco dot NOSPAM at gmail dot com
4 years ago
I made this gist to provide a complete overview of the PHP session handler life cycle updated to version 7.0 or above. In particular, I wanted to emphasize what methods and in what order are called when the native PHP functions are used for session management.

https://gist.github.com/franksacco/d6e943c41189f8ee306c182bf8f07654

I hope this analysis will help all the developers interested in understanding in detail the native session management performed by PHP and what a custom session handler should do.
Any comment or suggestion is appreciated.
up
1
RomanV
7 months ago
Be aware that when you inherit from \SessionHandler you implicitly disable session strict mode because it doesn't implement SessionUpdateTimestampHandlerInterface (see: https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode) thus validateId() is not called.

It's CRUCIAL to call the session_regenerate_id() function whenever any privilege level is changed (i.e. during authentication, password/permission/user role change) to mitigate the session fixation attack!
up
-3
jeremie dot legrand at komori-chambon dot fr
8 years ago
Here is a wrapper to log in a file each session's operations. Useful to investigate sessions locks (which prevent PHP to serve simultaneous requests for a same client).
Just change the file name at the end to dump logs where you want.

class DumpSessionHandler extends SessionHandler {
private $fich;

public function __construct($fich) {
$this->fich = $fich;
}

public function close() {
$this->log('close');
return parent::close();
}

public function create_sid() {
$this->log('create_sid');
return parent::create_sid();
}

public function destroy($session_id) {
$this->log('destroy('.$session_id.')');
return parent::destroy($session_id);
}

public function gc($maxlifetime) {
$this->log('close('.$maxlifetime.')');
return parent::gc($maxlifetime);
}

public function open($save_path, $session_name) {
$this->log('open('.$save_path.', '.$session_name.')');
return parent::open($save_path, $session_name);
}

public function read($session_id) {
$this->log('read('.$session_id.')');
return parent::read($session_id);
}

public function write($session_id, $session_data) {
$this->log('write('.$session_id.', '.$session_data.')');
return parent::write($session_id, $session_data);
}

private function log($action) {
$base_uri = explode('?', $_SERVER['REQUEST_URI'], 2)[0];
$hdl = fopen($this->fich, 'a');
fwrite($hdl, date('Y-m-d h:i:s').' '.$base_uri.' : '.$action."\n");
fclose($hdl);
}
}
ini_set('session.save_handler', 'files');
$handler = new DumpSessionHandler('/path/to/dump_sessions.log');
session_set_save_handler($handler, true);
up
-3
wei dot kavin at gmail dot com
5 years ago
php -S localhost:8000 -t foo/

touch index.php

vi index.php
============================================================
class NativeSessionHandler extends \SessionHandler
{
public function __construct($savePath = null)
{
if (null === $savePath) {
$savePath = ini_get('session.save_path');
}

$baseDir = $savePath;

if ($count = substr_count($savePath, ';')) {
if ($count > 2) {
throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath));
}

// characters after last ';' are the path
$baseDir = ltrim(strrchr($savePath, ';'), ';');
}

if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir));
}

ini_set('session.save_path', $savePath);
ini_set('session.save_handler', 'files');
}
}

$handler = new NativeSessionHandler("/var/www/foo");
session_set_save_handler($handler, true);
session_start();
$a = $handler->write("aaa","bbbb");var_dump($a);exit;

============================================================

output:bool(false)
To Top