Ваш сайт все более популярен, рекорды посещаемости бьются еженедельно. Вы подключаете кеширование, разбираетесь в нюансах настроек, оптимизируете. Но наступает момент когда одного сервера уже никак не достаточно, а переход на самую классную в мире железяку не находит полного понимания у начальства. Ах да, ID пользователя Вы храните в Сессии PHP в файле, и вроде бы уже морально готовы положить все в базу данных, как рекомендует Интернет… Но что-то Вас останавливает.
Сомнения!
Сомнения в целесообразности использования базы данных для хранения Сессий. Сессий, в которых всего-то пара элементов — ID пользователя да его роль. А то и вовсе — только ID админа. Вам не дают покоя накладные расходы на сеть, новые сущности и CAP-теорема. Каждую ночь Вас преследует один и тот же кошмар – не забыть букву B в слове Memcached… Все, давайте уже рассмотрим альтернативу — хранение ID клиента на его стороне, в Куки.
Итак, постановка задачи. Чтоб
- Масштабируемо
- Безопасно
- Быстро
- Просто
Автор уважает краткость и весьма вольно применяет принцип «Порядок и беспорядок — это вопрос количества.»
Решение
Выделяем в Куках две переменные — одна будет хранить публичный ключ, другая данные. В коде программы прописываем бессмысленную строку текста, это будет наш секретный ключ. При инициализации генерируем Initialization Vector — наш публичный ключ. Данные пишем и читаем через симметричное шифрование, применяя оба этих ключа. Не забудем про сериализацию. Она позволит хранить данные разных типов. Профит. Вы только что стали на шаг ближе к Shared-nothing архитектуре.
Плюсы и минусы
С масштабированием тут все в порядке — оно ограничено лишь фильтрацией Кук на стороне клиента. Передача пары через запрос решает эту проблему. Таким образом, любой случайный сервер из вашего пула будет способен корректно обработать запрос, а балансировщик трафика может тупеть до сетевого уровня той самой модели. Проблема периодической замены секретного ключа решается наличием двух ключей в программе. Пробуем их по порядку.
Теперь улучшим защиту. Время жизни Кук сделаем равным 0. Тогда закрытие браузера приведет к регенерации публичного ключа в следующий раз. Для усложнения расшифровки применим «длинный» алгоритм с ключом от 256 бит. Далее привяжем публичный ключ к IP клиента. Например так, ибо md5 и MCRYPT_RIJNDAEL_256 прекрасно дополняют друг друга:
$secret = md5(md5('My secret key') . md5($_SERVER['REMOTE_ADDR']));
Против создания базы для криптоанализа задействуем команду sleep при каждой неудачной попытке входа. Безопасники могут заменить на die и блокировать IP вместе с аккаунтом. Выслать вертолет…
Симметричное шифрование быстрее сетевого взаимодействия. Вы еретик, если дергаете базу, даже локальную, даже в оперативной памяти, даже с постоянным соединением или через сокет, при каждом клике, когда это можно не делать. „Привет, %ID%” мы можем показать немедленно, без накладных расходов.
Работа с Куки в приложении сложнее чем с сессией. Особенно, если последняя применяется как мини-база данных, а вывод на экран в разных местах программы. Мы знаем, что это плохая практика, темное прошлое осложнит Ваш путь к светлому будущему. Так пусть же популярность Вашего сервиса станет мотиватором для рефакторинга! Ведь Вы нужны Вашим клиентам, а они нужны Вам. Определенно, с MVC фреймворками все проще.
Нужно больше кода!
Класс для Zend Framework 1.x, который реализует Auth Cookie Storage — тут. Даже если Вы не применяете ZF на высоких нагрузках (хм, автор почему-то уверен в этом), то пара сотен строк кода с комментариями достаточны джедаю средней ээээ руки для понимания и воспроизведения принципа в любимом фреймворке или session_set_save_handler.
Вторник. Автор будет признателен за любые найденные где угодно ошибки.
Автор: olku