PHP cессии
Введение
HTTP протокол сам по себе stateless.
Сессии нужны для хранения информации о пользователях. Они используют cookie делают примерно то же что и
cookies
но лишены большинства их
недостатков
Сессии это инструмент для управления состоянием приложения.
Как и $_COOKIE $_SESSION это суперглобальная переменная
(
массив
)
, которая хранится на веб сервере.
У каждой сессии есть уникальный номер Session ID или SID
Сессия должна быть начата до отправки данных браузеру.
Данные на сервере и куки в браузере работают в связке. SID хранится в куки.
Сервер «помнит» пользователя пока работа с сайтом ведётся в сессии.
Сессия удаляется автоматически при закрытии браузера.
рассмотрим эти задачи в следующем параграфе
Что можно сделать с помощью сессий
- Хранить данные о пользователях на сервере. Это безопаснее чем куки. Данные хранятся в файлах или в Базе Данных
- Данные, которые часто используются, можно сохранить в сессии. Тогда обращаться к ним будет быстрее чем к базе данных.
- Хранить неограниченное количество информации. Нет лимита в 4KB как у куки.
- Сессии не отправляют одну и ту же информацию по кругу как куки - меньше размер запроса.
Пример
Из обычной жизни: когда вы получаете счёт за электричество, там содержится много разной информации. Но чтобы оплатить его, нужен только номер счёта. Вводите номер счёта и всё остальное подтянется с сервера.
Пример запроса от клиента
Request 1 GET /homepage.php HTTP/1.1 Host: eth1.ru Accept: */*
Рекомендуется отпрвлять Session ID с сервера в виде куки. Другой вариант: Session ID можно передать в URL параметре.
Response 1 HTTP/1.1 200 OK Content-type: text/html Set-Cookie: SID
Запрос к другой странице будет уже с SID
Request 2 GET /aboutus.php HTTP/1.1 Host: eth1.ru Cookie: SID Accept: */*
Примерно таким образом куки позволяют запоминать пользовательские настройки
Где настраивать сессии
Настроек более 40, способов их задавать тоже несколько.
Первое место, где можно задавать настройки сессий это файл
php.ini
.
С помощью
php.ini
можно изменить настройки для всего PHP сервера.
В Apache можно использовать файл
httpd.conf
для всего сервера и
.htaccess
для отдельных директорий.
Возможно, вы уже пользовались .htaccess для перенаправления запросов
Следующий способ задания настроек - функция ini_set(). Её нужно будет вызывать со всеми скриптами, где используются сессии, поэтому способ не очень эффективный.
Session Configurations Options
Полный список доступен на http://php.net
session.auto_start: автоматически начинает сессию.
По умолчанию выключена.
session.name: задаёт имя текущей сессии и сессионной куки
По умолчанию PHPSESSID.
Может быть изменено с помощью функции
session_name()
session.save_path: путь по которому сохраняется информация о сессии
По умолчанию tmp директория сервера.
session.gc_maxlifetime: максимальное время жизни
По умолчанию 1440 секунд (24 минуты).
session.cookie_lifetime: время жизни куки, которая отправляется браузеру. По сути это значение, которое мы добавляем к time() когда
задаём
expires
По умолчанию 0.
session.cookie_path: задаёт
cookie path
По умолчанию '/'.
session.cookie_secure: задаёт
secure
Если включить то куки будут отправляться только по HTTPS.
По умолчанию выключена.
session.use_strict_mode:
если включить то SID которые созданы не сервером будут отклонены.
По умолчанию выключена.
session.cookie_httponly: задаёт
httponly
Если включить куки будет доступна только по HTTP (и HTTPS). То есть
JavaScript
или
bbscript не смогут получить к куки доступ
По умолчанию выключена.
session.use_cookies: указывает нужно ли сохранять SID в
cookies
на стороне клиента.
По умолчанию включена.
session.use_only_cookies: заставляет сессию использовать
только
cookie
для хранения SID. Работает совместно с session.use_cookies
По умолчанию включена.
session.use_trans_sid: контролирует использование «прозрачных»
SID
Если включить - SID будет добавляться как параметр прямо в URL. Например:
https://www.eth1.ru/?SID
Эту опцию обычно включают только тогда, когда нет поддержки cookies
По умолчанию выключена.
Пользуйтесь trans sid с осторожностью так как это может поставить под угрозу безопасность пользователя.
- Пользователь может отправить URL содержащий активный SID другому человеку по email, irc и т.п.
- URL содержащая активный SID может быть сохранён на публично доступном компьютере.
- - Пользователь может заходить на ваш сайт с одним и тем же SID который он сохранил в закладках или истории браузера.
Подробности на http://php.net/session.use-trans-sid
session.cache_limiter: указывает способ контроля за кэшем во время сессии.
Доступные варианты:
- nocache
- private
- private_no_expire
- public
По умолчанию nocache.
Для сессий с аутентификацией нужно, чтобы кэширование в браузере было отключено.
Подробнее:
http://php.net
http://php.net
session.cookie_samesite: контролирует доступности куки в кроссдоменных запросах.
Доступные варианты: Lax и Strict
Кросдоменные запросы могу быть не только от злоумышленников. Их может делать ваш сайт с разных поддоменов. Либо вы можете следить за перемешением пользователя между своими проектами.
Функции
Полный список доступен на php.net
session_start(): начинает сессию и делает доступной переменную $_SESSION.
session_name(): переименовывает сессию.
Меняет значение, заданное с помощью функции
session.name.
session_id(): получает или устанавливает текущий session id
set new session ID: session_id('<new SID>')
session_destroy(): удаляет всю информацию записанную в сессию
Пример .htaccess
Настройки вашего сервера могут отличаться, это просто пример
# Use php_flag for boolean values, # php_flag session.auto_start off php_flag session.use_cookies on php_flag session.use_only_cookies on php_flag session.use_trans_sid off php_flag session.use_strict_mode on # Use php_value for non-boolean values # Enclose values that contain punctuation in quotes. php_value session.cache_limiter nocache php_value session.cookie_samesite Lax php_value session.name AOSESSID php_value session.save_path ./tmp # Cookie settings php_value session.cookie_path '/' php_value session.cookie_lifetime 0 php_flag session.cookie_httponly on php_flag session.cookie_secure on
Инициализация сессии
Сессия начинается с помощью функции
session_start()
Эта функция должна быть включена во все скрипты, в которых вам нужно использовать сессии.
Сперва выполняет проверку наличия активной сессии. Если сессии нет - начинает новую.
Сохраняет данные в супер глобальной переменной
$_SESSION,
которая является
массивом
session_start();
$_SESSION['username'] = 'Andrei';
echo isset($_SESSION['username']) ? $_SESSION['username'] : 'Not available';
Если последняя строка вам непонятна - прочитайте статью
«Тернарный оператор в PHP»
Демонстрацию работы session_start() в вашем браузере вы можете сделать на
этой странице
Посмотреть куки можно в Chrome DevTools → Application → Cookies
Выберите eth1.ru или eth1.ru и найдите AOSESSID
Изучить файл с данными о сессии можно в директории, которую вы указали в
php_value session.save_path
В моём .htaccess указан
php_value session.save_path ./tmp
Поэтому я смотрю содержимое там
cat ./tmp/sess_d510566d6fc7dcbf9a9c21eb29d846ce
username|s:6:"Andrei";
Удаление сессии
Сессия истекает когда закрывается браузер, наступает таймаут, её явно делают просроченной.
Если не удалить данные, они так и будут лежать на сервере - это небезопасно.
Уничтожение сессии включает в себя:
- Удаление всех перемменых в сессии
- Удаление данных на сервере
- Удаление куки в клиенте
session_destroy()
Удаляет все данные привязанные к сессии.
Не удаляет никаких переменных из суперглобальной переменной
$_SESSION.
Не удаляет куки.
Возвращает boolean - удалились данные или нет
Если использовать только session_destroy() можно переиспользовать $_SESSION просто вызвав session_start()
unset()
unset() это стандартная PHP функция, которую использую не только с сессиями.
Чтобы очистить username нужно выполнить
unset($_SESSION['username']);
Чтобы очистить всё можно обойтись без unset()
$_SESSION = [];
Пример полного удалёния сессии
$_SESSION = [];
$params = session_get_cookie_params();
$options = array('lifetime' => time() - 60);
setcookie(session_name(), '', $options);
session_destroy();
Хранение сессий в базе данных
По умолчанию PHP хранит каждую сессию в файле в
session.save_path. Чтобы хранить сессии в базе данных,
пишут класс, который реализует встроенный интерфейс
SessionHandlerInterface, и передают его в
session_set_save_handler(). Обычно такой класс объявлен в
пространстве имён, и отсюда берётся первая
ошибка.
Примеры ниже запущены на PHP 8.2 в образе php:8.2-cli
Docker, текущая папка смонтирована как
/app. База данных —
SQLite в памяти, сервер не нужен.
Глобальные классы внутри пространства имён
Внутри пространства имён PHP ищет имя класса без ведущей обратной косой черты относительно этого пространства. PDO в этом файле превращается в App\Session\PDO:
db.php
<?php
namespace App\Session;
$db = new PDO('sqlite::memory:');
echo get_class($db), "\n";
php db.php
Fatal error: Uncaught Error: Class "App\Session\PDO" not found in /app/db.php:5 Stack trace: #0 {main} thrown in /app/db.php on line 5
Интерфейс — тоже глобальное имя. Класс-обработчик, который реализует его без импорта, нельзя даже объявить:
handler.php
<?php
namespace App\Session;
class DbSessionHandler implements SessionHandlerInterface
{
}
php handler.php
Fatal error: Uncaught Error: Interface "App\Session\SessionHandlerInterface" not found in /app/handler.php:5 Stack trace: #0 {main} thrown in /app/handler.php on line 5
То же самое относится к любому глобальному классу, интерфейсу и константе класса: PDOException, PDO::FETCH_ASSOC, DateTime. Импортируйте каждое имя один раз под строкой с namespace:
<?php
namespace App\Session;
use PDO;
$db = new PDO('sqlite::memory:');
echo get_class($db), "\n";
php db.php
PDO
Имя с ведущей обратной косой чертой, new \PDO(...), тоже работает и выводит то же самое. Импорт удобнее, когда имя встречается в классе несколько раз.
Типы возвращаемых значений начиная с PHP 8.1
Как только интерфейс найден, PHP сверяет с ним каждый метод. Начиная с PHP 8.1 у методов SessionHandlerInterface есть типы возвращаемых значений, и обработчик, написанный для старого PHP, с методами вроде public function open($path, $name), при загрузке файла выдаёт по одному предупреждению на метод:
Deprecated: Return type of App\Session\DbSessionHandler::open($path, $name) should either be compatible with SessionHandlerInterface::open(string $path, string $name): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 14 Deprecated: Return type of App\Session\DbSessionHandler::close() should either be compatible with SessionHandlerInterface::close(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 19 Deprecated: Return type of App\Session\DbSessionHandler::read($id) should either be compatible with SessionHandlerInterface::read(string $id): string|false, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 24 Deprecated: Return type of App\Session\DbSessionHandler::write($id, $data) should either be compatible with SessionHandlerInterface::write(string $id, string $data): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 31 Deprecated: Return type of App\Session\DbSessionHandler::destroy($id) should either be compatible with SessionHandlerInterface::destroy(string $id): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 37 Deprecated: Return type of App\Session\DbSessionHandler::gc($max_lifetime) should either be compatible with SessionHandlerInterface::gc(int $max_lifetime): int|false, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 42
Сервер, у которого в error_reporting нет E_DEPRECATED, эти сообщения не показывает, и обработчик кажется исправным до той версии PHP, в которой типы станут обязательными. Скопируйте сигнатуры из интерфейса вместо того, чтобы добавлять #[\ReturnTypeWillChange].
Рабочий обработчик
Класс с обоими исправлениями: глобальные имена импортированы, у каждого метода те же типы, что в интерфейсе. Столбец updated позволяет gc() удалять истёкшие сессии:
DbSessionHandler.php
<?php
namespace App\Session;
use PDO;
use SessionHandlerInterface;
class DbSessionHandler implements SessionHandlerInterface
{
public function __construct(private PDO $db)
{
}
public function open(string $path, string $name): bool
{
return true;
}
public function close(): bool
{
return true;
}
public function read(string $id): string|false
{
$stmt = $this->db->prepare('SELECT data FROM sessions WHERE sid = ?');
$stmt->execute([$id]);
return (string) $stmt->fetchColumn();
}
public function write(string $id, string $data): bool
{
$stmt = $this->db->prepare('REPLACE INTO sessions (sid, data, updated) VALUES (?, ?, ?)');
return $stmt->execute([$id, $data, time()]);
}
public function destroy(string $id): bool
{
return $this->db->prepare('DELETE FROM sessions WHERE sid = ?')->execute([$id]);
}
public function gc(int $max_lifetime): int|false
{
$stmt = $this->db->prepare('DELETE FROM sessions WHERE updated < ?');
$stmt->execute([time() - $max_lifetime]);
return $stmt->rowCount();
}
}
Для неизвестного id сессии read() возвращает пустую строку, и PHP считает это новой пустой сессией. Тестовый скрипт создаёт таблицу, добавляет одну давно истёкшую сессию, запускает сессию через обработчик и вызывает gc():
test.php
<?php
require __DIR__ . '/DbSessionHandler.php';
$db = new PDO('sqlite::memory:');
$db->exec('CREATE TABLE sessions (sid TEXT PRIMARY KEY, data TEXT, updated INTEGER)');
$db->exec("INSERT INTO sessions VALUES ('old1', 'username|s:3:\"Ann\";', 0)");
$handler = new App\Session\DbSessionHandler($db);
session_set_save_handler($handler, true);
session_id('demo1');
session_start();
$_SESSION['username'] = 'Andrei';
session_write_close();
foreach ($db->query('SELECT sid, data FROM sessions ORDER BY sid') as $row) {
echo $row['sid'], ' ', $row['data'], "\n";
}
echo 'gc removed: ', $handler->gc(1440), "\n";
echo 'left: ', $db->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), "\n";
php test.php
demo1 username|s:6:"Andrei"; old1 username|s:3:"Ann"; gc removed: 1 left: 1
В строке сессии та же сериализованная строка, что и в файле сессии из главы про session_start(). Класс работает только через PDO, поэтому для другой базы данных нужны другая строка подключения и таблица sessions с типами столбцов этой базы.
Автор статьи: Андрей Олегович