Подтвердить что ты не робот

Драйвер сеанса cookie не сохранит никаких ошибок проверки или данных флэш-памяти

Мне тяжело работать с драйвером сеанса cookie в Laravel.

У меня простая форма с проверкой на месте. Это мой метод для сохранения данных этой формы:

public function store()
{
    $this->validate(request(), [
        'name'        => 'required',
        'title'       => 'required',
        'description' => 'required|max:600',
        'image'       => 'required|file|mimes:jpeg,png',
    ]);

    $member = TeamMember::create(request()->all());
    $member->addImage(request()->file('image'));

    return redirect()->route('backoffice.team-members');
}

Довольно просто.

Проблема заключается в том, что при использовании драйвера сеанса cookie, если я сохраню эту форму с описанием длиной в 1024 символа, я буду перенаправлен обратно, но без данных флэш-памяти и не будет $errors в представлении для следующего запрос на обработку.

Пример:

введите описание изображения здесь

Это POST после использования этой строки:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce gravida eros ut leo commodo luctus. Nulla neque dui, laoreet quis felis in, porta tincidunt felis. Phasellus in lacus et sem condimentum ornare. Praesent vitae nisi tempus, gravida tortor eu, convallis dui. Cras lacinia posuere scelerisque. Vestibulum tincidunt purus id sollicitudin varius. Sed eros urna, mattis nec nunc eu, finibus suscipit ipsum. Aliquam varius faucibus congue. Vivamus convallis imperdiet sem a commodo. Proin cursus feugiat sem a pharetra. Curabitur rhoncus non quam sit amet lacinia. Sed ut nisl id odio faucibus vehicula vel ut erat. Vestibulum ut iaculis magna. Quisque sit amet massa sodales, suscipit nisl eu, dapibus elit. Morbi posuere ligula pretium commodo semper. Nam odio elit, rutrum finibus tortor eget, viverra viverra metus. Proin tincidunt tempor ex pretium rhoncus. Proin egestas erat sed eros congue, mollis gravida magna bibendum. Pellentesque vel bibendum nunc. Orci varius natoque penatibus et magnis dis viverra fusce.

В поле описания. 1024 байта, если быть точным.

Вместо этого, если я просто заполню поле дополнительными фиктивными данными, но ничего сумасшедшего:

введите описание изображения здесь

Если я изменил драйвер сеанса на файл:

введите описание изображения здесь

... он работает.

Но это не устраняет мою проблему. Мне нужно использовать драйвер cookie для сеанса, поскольку веб-сайт выпуска работает в трех разных датацентрах для достижения высокой доступности. Использование cookie для сеанса позволяет пользователю нажимать на любой из 3-х серверов и по-прежнему продолжать его запрос без необходимости использовать какой-либо липкий сеанс или любой центральный драйвер сеанса.

Использование базы данных в качестве драйвера, также находящегося в кластере с HA, не является вариантом, так как это веб-сайт с очень высоким трафиком, и это будет запись по запросу, которая не кажется привлекательной вообще. Я хотел бы предотвратить это любой ценой.

Во всяком случае это можно решить?

Я должен сказать, что это backoffice на веб-сайте, но вскоре пользователь в интерфейсе сможет также писать более 1024 символов в текстовом поле... так что если я просто изменил драйвер для backoffice, помогите, как мы будем работать в том же для наших пользователей.

4b9b3361

Ответ 1

Драйвер сеанса cookie не подходит для приложений, которые должны хранить любой значительный объем данных в сеансе пользователя. Браузеры обычно ограничивают данные, хранящиеся в одном cookie, примерно до 4 КБ (4096 байт). Как мы выяснили, мы можем легко исчерпать эту способность, пытаясь сохранить строку длиной 1024 символа в файле cookie сессии, строка "Lorem ipsum..." в вопросе содержит только символы ASCII, и каждый символ ASCII представляется с использованием 4 байта, поэтому 1024 & times; 4 = 4096 байт.

Как мы видим, мы быстро теряем пространство, когда нам нужно хранить дополнительные элементы в одном cookie сеанса, такие как метаданные сериализации для значений PHP, или когда данные содержат символы UTF-8, которые потребляют больше, чем 4 байта на символ. Чтобы продолжить использование файлов cookie для хранения данных сеанса более 4 КБ, нам нужно написать специальный драйвер сеанса, который разделяет данные сеанса на несколько файлов cookie для каждого ответа и затем собирает их по каждому запросу. По уважительной причине, ни один из существующих пакетов не делает этого, о котором я знаю. Это подводит меня к следующему пункту...

Я хочу сильно отказать в использовании файлов cookie для хранения полного сеанса (а не только идентификатора сеанса). Этот подход позволяет выявлять уязвимости безопасности, добавлять накладные расходы на запросы и ответы (включая запросы на статические ресурсы в одном домене), подвергать риску десинхронизацию данных (щелчок правой кнопкой мыши на вкладке приложения и rarr; дублировать, чтобы увидеть, что происходит), усложняет тесты и отладки и вызывает проблемы при хранении определенных видов данных.

Для распределенного приложения в вопросе у нас есть четыре варианта:

  • Храните данные сеанса на центральном сервере, к которому обращаются каждый экземпляр приложения.
  • Сохранять состояние сеанса на каждом сайте и реплицировать его между сайтами
  • Используйте "липкие" сеансы для привязки пользователя к одному сайту после запуска сеанса.
  • Напишите нетривиальное количество кода для реализации пользовательского драйвера сеанса, который позволяет клиенту передавать состояние сеанса без использования файлов cookie или файлов, которые разделяют файлы cookie

Я думаю, что мы можем разумно устранить четвертый вариант. Если у нас нет веской причины избегать использования первых трех подходов, а для типичных приложений мы обычно этого не делаем, мы не можем оправдать объем работы, сложности и накладные расходы, необходимые для создания системы для передачи данных сеанса взад и вперед по всем HTTP, когда первые три варианта являются стандартными, широко принятыми решениями.

Основываясь на информации в вопросе, мне кажется, что вы уже понимаете понятия и последствия других трех вариантов, поэтому я не буду подробно объяснять каждому из них (но сделайте комментарий, чтобы сообщить мне, если я должен).

Для приложений без дополнительных требований к инфраструктуре я рекомендую третий подход: липкие сеансы. Они относительно просты в настройке и обычно требуют настройки на балансировщике нагрузки, так что, как только клиент начинает сеанс с сервера приложений, балансировщик нагрузки направляет любые последующие запросы на тот же сервер до окончания сеанса. Для высокой доступности мы можем комбинировать этот подход с Redis session driver и Redis, настроенные для репликации master-slave между центрами данных и, необязательно, Redis Sentinel для автоматизации восстановления после сбоя. Redis хорошо подходит к данным сеанса и обеспечивает лучшую производительность, чем реляционная база данных. Если у нас есть несколько экземпляров приложений в каждом центре обработки данных, Redis обеспечивает центральное расположение для данных сеанса для всех экземпляров на одном сайте.

Для полноты и прямого ответа на вопрос, здесь приведен обзор для разработки, необходимой для создания драйвера сеанса на основе файлов cookie, который обрабатывает данные сеанса больше 4 КБ. Опять же, я рекомендую один из других способов, описанных выше:

  • Сначала нам нужно создать новый класс, который реализует PHP SessionHandlerInterface (проверьте Laravel обработчик сеанса cookie для начальной точки, возможно, мы можем расширить этот класс).

  • write()методу этого класса необходимо будет сериализовать сеанс $data, а затем разделить данные на куски размером менее 4 КБ (для некоторых браузеров - менее 4093 байта). Обязательно учитывайте многобайтовые символы. Прежде чем разделить данные, мы также можем зашифровать его, если сеанс содержит конфиденциальную информацию или если мы не хотим, чтобы умные пользователи возились со значениями. Затем метод должен добавить новый файл cookie для каждого фрагмента данных сеанса. Каждому файлу cookie необходимо будет содержать последовательность в своем имени, и мы можем добавить дополнительный файл cookie, содержащий количество фрагментов.

  • Метод read() выполнит эти операции в обратном порядке. Во-первых, он будет собирать каждый кусок из файлов cookie в запросе, используя значение cookie, которое содержит количество фрагментов, а затем, при необходимости, расшифровывать данные, если мы их зашифровали.

  • Метод destroy() должен очищать содержимое каждого файла cookie, содержащего фрагмент.

  • Затем мы выберем имя для драйвера сеанса, например cookie-extended, а в методе boot() поставщика услуг зарегистрировать его как доступный драйвер, используя:

    Session::extend('cookie-extended', ...);
    

    Нам нужно указать приложение в config/session.php или в .env для использования нового драйвера для пользовательских сеансов.

Если вы решите пойти по этому пути, обязательно протестируйте реализацию в каждом браузере, который вы планируете поддерживать, поскольку разные браузеры накладывают свои ограничения на размер и количество файлов cookie. Даже если мы увеличили количество данных сеанса, которые мы можем хранить как файлы cookie, мы по-прежнему ограничиваемся максимальным количеством файлов cookie, которые браузер будет принимать за каждый домен.

Как последнее замечание, хранение сеансов в базе данных может не повлиять на производительность так сильно, как вы думаете. Возможно, стоит измерить фактическую нагрузку, создаваемую этим простым решением, прежде чем инвестировать много часов в оптимизацию для беспокойства, которое может не быть реальной проблемой.

Ответ 2

В браузере есть ограничение на файл cookie. Он слишком велик, чтобы браузер сохранял файл cookie, когда вы используете cookie в качестве драйвера сеанса. Вы можете проверить ограничение cookie браузера здесь http://browsercookielimits.squawky.net/

Рекомендация:
Используйте redis в качестве драйвера сеанса.

Действия:
 1.После установки Redis, добавьте пакет predis/predis:

composer require predis/predis

2. Измените конфигурационный файл config/database.php:

'redis' => [

    'client' => 'predis',

    'default' => [
        'host' => env('REDIS_HOST', 'localhost'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => 0,
    ],

],