время жизни сессии spring boot

Контролируем и сохраняем сессии, используя Spring

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

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

И нужно учесть 2 возможности инвалидации сессии:

Сохранение сессий при перезагрузке

Для начала нужно научиться создавать и сохранять сессии(сохранять будем в бд, но возможно сохранять и в redis, например). В этом нам поможет Spring security и spring session jdbc. В build.gradle добавляем 2 зависимости:

Создадим свой WebSecurityConfig, в котором включим сохранение сессий в бд с помощью аннотации @EnableJdbcHttpSession

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

Для сохранения сессий в бд так же необходимо в application.yml добавить проперти(в моем проекте используется postgresql):

Можно также указать время жизни сессии(по умолчанию 30 минут) с помощью проперти:

Если не указать суффикс, то по умолчанию будут использоваться секунды.

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

Ограничиваем количество сессий

Для ограничения количества сессий мы используем свою кастомную стратегию. Для ограничения в принципе было бы достаточно прописать в конфиге:

Однако нам необходимо дать выбор пользователю(закрыть прошлую сессию или не открывать новую) и сообщать администратору о решение пользователя(если он выбрал закрыть сессию).

Наша кастомная стратегия будет наследником.

ConcurrentSessionControlAuthenticationStrategy, которая и позволяет определить превышен ли лимит сессий у пользователя или нет.

Осталось описать удаление активных сессий, кроме текущей. Для этого в имплементации SessionsManager реализуем метод deleteSessionExceptCurrentByUser:

Обработка ошибок при превышении ограничения сессий

Как можно заметить, при отсутствии параметра force(или когда он равен false) мы бросаем исключение SessionAuthenticationException из нашей стратегии. Мы бы хотели вернуть фронту не ошибку, а 300 статус(чтобы фронт знал, что нужно показать сообщение пользователю для выбора действия). Для этого реализуем перехватчик, который мы добавили в

Заключение

Управление сессиями оказалось не такое страшное, как представлялось в начале. Spring позволяет гибко настраивать свои стратегии для этого. А с помощью перехватчика ошибок можно вернуть фронту любое сообщение и статус.

Надеюсь, что эта статья будем кому-нибудь полезна.

Источник

Совершенствуем контроль сессий в Spring Security

Добрый день, уважаемое Сообщество.

Разрабатывая многопользовательское web-приложение, столкнулся с проблемой многократного входа в систему (новый login при незавершенной старой сессии), решение которой потребовало необычного обходного маневра, чтобы сохранить логичную работу программы и ее понятный дизайн. В этой статье хочу поделиться c Вами опытом, осветив сперва традиционные подходы к управлению сессиями в Spring Security, и завершив обзор рацпредложением в виде ‘костыля’ собственной разработки.

Проблема контроля сессий актуальна для множества проектов. В моем случае это была игра (бэкенд на Java+Spring), где зарегистрировавшиеся пользователи могут выбирать с кем сразиться из списка присутствующих на сайте свободных игроков. После входа (login) игрока информация о нем добавляется в структуру данных в памяти. Часть этих данных асинхронно отображается в игровом интерфейсе, как список игроков, присутствующих на арене. Когда игрок выходит, то информация о нем должна быть сохранена в БД, удалена из структуры данных, и игрок более не будет отображаться в списке соперников online. Здесь возникали некоторые трудности из-за асинхронности, но не будем затрагивать их, ведь они лежат в стороне от темы статьи.

Остановимся подробнее на стратегии управления самыми различными ситуациями, связанными с login и logout. Прежде всего нужно было учесть то, что выход игрока с арены может произойти в результате таких его действий:

Уходим по-аглийски

Для таких ‘английских’ сценариев используется следующий подход.

1. Добавляется SessionEventListener при регистрации DispatcherServlet в ходе стандартной инициализации и настройки Spring MVC приложения:

2. Реализуется слушатель событий сессии:

3. Добавляется SessionRegistry в конфигурацию Spring Security:

Теперь, благодаря тому, что мы устанавливаем таймаут ‘event.getSession().setMaxInactiveInterval(60*10)’ для каждой новой сессии (в SessionEventListener ), у нас любой сценарий выхода по-английски будет приводить к тому, что через короткое время (у нас в примере — 10 минут) сессия становится expired. Сразу же будет выброшено событие sessionDestroyed, оно будет обработано слушателем, который вызовет соотвествующий сервис для удаления игрока с арены, сохранения его persistent данных, очистки кэшей и т.п. То, что мы и хотели. Разместив всю эту логику в единственном методе, вызываемом из обработка sessionDestroyed, мы значительно упрощаем дизайн.

Логинищимуся — свободу выбора

До сих пор Spring Security демонстрировал необходимую гибкость. Но вот тут возникло желание точно также учесть различные варианты поведения пользователя при авторизации. Так, игрок может:

Читайте также:  Кто выплачивает дивиденды по акциям ежеквартально

Что предлагает в данном случае стандартный подход Spring Security. Установить при конфигурации следующие свойства:

Это как раз не устаивало. Так как такое поведение Spring Security было подобно превентивной ядерной бомбардировке. Игрок возможно по ошибке логиниться повторно, и его прошлая игра без предупреждения прекращается. Необходимо было доработать этот сценарий, чтобы для игрока было показано предупреждающее окно с возможность выбора:

Теперь в результате настройки ‘.maxSessionsPreventsLogin(true)’ повторный логин игрока при незакрытой прошлой сессии приводит к определенней в Spring Security исключительной ситуации SessionAuthenticationException. Нам следует только обработать ее и перенаправить пользователя на html страницу с предупреждением, которая, кроме того, задает выбор: а) не продолжать и вернуться к прошлой открытой сессии (где возможно идет игра); б) все-таки залогиниться и тогда прошлая сессия должна быть убита.

Обработчик такой исключительной ситуации регистрируется при конфигурации Spring Security как ‘.failureHandler(new SecurityErrorHandler())’, а сам класс обработчика реализуется следующим образом:

Позволь, я отрублю сессии голову

Хотя такой подход неоднократно описан в сообществе Spring Security, он имеет существенный недостаток. При его реализации не происходит интуитивно ожидаемого действия. Сессия конечно же объявляется устаревшей (expired), но не закрывается. Другими словами, сессия не будет уничтожена (destroyed), после того, как мы вручную вызвали для нее рекомендованный expireNow(). А значит:

Загнанные сессии пристреливают, не правда ли?

Почему так происходит. Вызов метода expireNow() у объекта SessionInformation просто напросто устанавливает значение его поля expired=true. Никаких других действий не выполняется и не должно выполняться. Только когда пользователь из своей устаревшей сессии отправит какой-либо новый HTTP запрос, то тогда эта expired сессия будет убита, а пользователь увидит, как в его браузере произошел редирект на страницу ввода login, обработает событие sessionDestroyed (ожидаемое поведение). Это связано с тем, что: а) уничтожением сессии занимается контейнер сервлетов и делает он это в данном случае после получения нового HTTP запроса; б) функционал Spring Security реализованный за счет цепочек фильтров (Java Servlet Filter) без получения запроса ничего не выполняет; в) добавленный нами к сервлету слушатель SessionEventListener обработает событие sessionDestroyed тоже вследствие нового HTTP запроса.

Рекомендованный многими, включая Spring документацией, метод для контроля сессий ‘expireNow()’, таким образом, работает вопреки наивным ожиданиям. В нашим случае это нарушало синхронность приложения. Важно, что повторный логин после ‘expireNow()’ уже возможен, так как контроль сессий Spring Security разрешает это после того, как прошлая сессия была объявлена expired=true (исключения SessionAuthenticationException уже не выбрасываются). Spring документация говорит об этом достаточно поверхностно. При этом прошлая сессия фактически не уничтожена, событие sessionDestroyed не обработано, соответственно, информация об игроке, который ожидает, что он вышел (чтобы возможно заново залогиниться), не сохранена. Игра (как и чат или другое интерактивное приложение) посылают сообщения в старую сессию и т.п. Если игрок теперь залогинится заново произойдет хаос в связи с конкурентным созданием новой сессии и отработкой sessionDestroyed, разбираться с которым можно тяжеловесными threadsafe инструментами. Но можно все сделать проще.

Чтобы исправить эту ситуацию и сделать логику повторного логина и закрытия старой сессии более предсказуемой был использован следующий подход. В наш SessionService (бин назван как ‘expireUsereService’) мы добавляем следующий метод:

Благодаря вызову этого метода мы симулируем http запрос от пользователя, сессия которого была нами же помечена как устаревшая. Лучше вызвать ‘killExpiredSessionForSure(id)’ сразу после ‘expireNow()’, тогда будет происходить желаемое поведение:

Итоги подведем

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

Кроме того, данный подход, то есть использование комбинации вызова методов — общеизвестного ‘expireNow()’ и предложенного ‘killExpiredSessionForSure(String id), можно использовать и в таких случаях:

Примечание
* — Переход происходит благодаря коду на фронтенде. В нашем случае текущие сообщения в ходе игры передаются с помощью WebSocket. WebSocket использует HTTP протокол (модифицированный) только для установления соединения, а затем обменивается сообщениями по своему WebSocket протоколу, работающему поверх TCP. Соответственно обмен этими сообщениями не фильтруется Servlet Filter вообще и цепочкой фильтров Spring Security в частности. Поэтому даже в просроченной (expired) сессии до нашего совершенствования шел обмен игровыми сообщениями. Передача таких сообщений не приводила к уничтожению expired сессии. Так возникала иллюзия продолжения игры там, где этого не должно было быть. Но если сессия окончательно уничтожена (с помощью вызова killExpiredSessionForSure(id)), то автоматически разрывается и WebSocket соединение. Фронтендовый код замечает это (при разрыве WebSocket соединения выполняется заданный callback) и переходит на home/login-page страницу. Это способ позволяет прервать WebSocket соединение бэкендом, так как реализация Stomp в Spring из коробки не имеет API для разрыва WebSocket сессии со стороны сервера.

Читайте также:  назначение квартиры жилое помещение

Источник

Контролируйте сеанс с помощью Spring Security

Настройка сеансов с помощью Spring Security – настройка параллельных сеансов, включение защиты от фиксации сеансов и предотвращение того, чтобы URL-адреса содержали информацию о сеансе.

1. Обзор

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

Дальнейшее чтение:

Получение информации о пользователе в Spring Security

Весенняя охрана Помнит Меня

Весенний выход из системы безопасности

2. Когда Создается Сеанс?

Мы можем точно контролировать, когда будет создан наш сеанс и как Spring Security будет взаимодействовать с ним:

По умолчанию Spring Security создаст сеанс, когда он ему понадобится – это ” ifRequired “.

3. Под капотом

Для атрибута strict create-session=”без состояния” эта стратегия будет заменена другой – NullSecurityContextRepository – и сеанс не будет создан или использован для сохранения контекста.

4. Управление Параллельными Сеансами

Первым шагом в включении параллельной поддержки session-control является добавление следующего прослушивателя в web.xml :

Или определите его как Боб – следующим образом:

Чтобы включить сценарий, допускающий несколько одновременных сеансов для одного и того же пользователя, в конфигурации XML следует использовать элемент :

Или через конфигурацию Java:

5. Тайм-аут сеанса

5.1. Обработка тайм-аута сеанса

Соответствующая конфигурация Java:

5.2. Настройте тайм-аут сеанса с помощью Spring Boot

Мы можем легко настроить значение тайм-аута сеанса встроенного сервера с помощью свойств:

Если мы не определим единицу длительности, Spring будет считать, что это секунды.

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

Наконец, важно отметить, что, несмотря на то, что Spring Session поддерживает аналогичное свойство для этой цели ( spring.session.timeout ), если это не указано, автоконфигурация вернется к значению свойства, которое мы впервые упомянули.

6. Запретить использование параметров URL для отслеживания сеансов

Раскрытие информации о сеансе в URL-адресе является растущим риском для безопасности (с 7-го места в 2007 году до 2-го места в 2013 году в списке Топ-10 OWASP).

В качестве альтернативы, начиная с Servlet 3.0, механизм отслеживания сеансов также может быть настроен в web.xml:

При этом выбирается, где хранить JSESSIONID – в файле cookie или в параметре URL.

7. Защита Фиксации Сеанса С Помощью Пружинной Защиты

Платформа обеспечивает защиту от типичных атак фиксации сеанса путем настройки того, что происходит с существующим сеансом, когда пользователь пытается снова пройти аутентификацию:

Соответствующая конфигурация Java:

По умолчанию Spring Security включает эту защиту (” migrateSession “) – при аутентификации создается новый сеанс HTTP, старый становится недействительным, а атрибуты из старого сеанса копируются.

Если это нежелательное поведение, доступны два других варианта:

8. Безопасный файл Cookie Сеанса

Далее мы обсудим, как защитить наш сессионный файл cookie.

Мы можем использовать флаги HttpOnly и secure для защиты нашего файла cookie сеанса :

Мы можем установить эти флаги для нашего файла cookie сеанса в web.xml :

Этот параметр конфигурации доступен начиная с Java servlet 3. По умолчанию http-only имеет значение true, а secure – false.

Давайте также посмотрим на соответствующую конфигурацию Java:

Если мы используем Spring Boot, мы можем установить эти флаги в нашем application.properties :

Наконец, мы также можем добиться этого вручную, используя Фильтр :

9. Работа С Сессией

9.1. Бобы с областью действия Сеанса

Компонент можно определить с помощью session scope, просто используя аннотацию @Scope для компонентов, объявленных в веб-контексте:

Затем боб можно просто ввести в другой боб:

И Spring свяжет новый компонент с жизненным циклом сеанса HTTP.

9.2. Ввод необработанного сеанса в контроллер

Необработанный HTTP-сеанс также может быть введен непосредственно в метод Controller :

9.3. Получение необработанной сессии

Текущий сеанс HTTP также может быть получен программно с помощью raw Servlet API :

10. Заключение

Источник

Руководство к весенней сессии

В статье представлен проект Spring Session, который отделяет управление сеансами от контейнера сервера.

1. Обзор

Spring Session имеет простую цель освободить управление сеансами от ограничений HTTP-сеанса, хранящегося на сервере.

Это решение позволяет легко обмениваться данными сеанса между службами в облаке, не привязываясь к одному контейнеру (например, Tomcat). Кроме того, он поддерживает несколько сеансов в одном браузере и отправку сеансов в заголовке.

Для ознакомления с Redis ознакомьтесь с этой статьей.

2. Простой Проект

Давайте также добавим некоторые свойства конфигурации для нашего сервера Redis в application.properties :

3. Конфигурация пружинной Загрузки

4. Стандартная конфигурация пружины (без загрузки)

Давайте также рассмотрим интеграцию и настройку spring-session без загрузки Spring – просто с помощью обычной пружины.

4.1. Зависимости

Во-первых, если мы добавляем spring-session в стандартный проект Spring, нам нужно будет явно определить:

4.2. Конфигурация Весенней сессии

Теперь давайте добавим класс конфигурации для Весенней сессии :

Теперь давайте завершим это приложение контроллером и конфигурацией безопасности.

Читайте также:  Ожидается очень грандиозная акция лексическая ошибка

5. Конфигурация приложения

Перейдите к нашему основному файлу приложения и добавьте контроллер:

Это даст нам конечную точку для тестирования.

Затем добавьте наш класс конфигурации безопасности:

Это защищает наши конечные точки с помощью базовой аутентификации и настраивает пользователя для тестирования.

6. Испытание

Наконец, давайте все проверим – здесь мы определим простой тест, который позволит нам сделать 2 вещи:

Давайте сначала все устроим:

Обратите внимание, как мы настраиваем оба этих клиента – HTTP-клиент и Redis-клиент. Конечно, на данный момент сервер (и Redis) должен быть запущен и запущен, чтобы мы могли общаться с ними с помощью этих тестов.

Давайте начнем с проверки того, что Redis пуст:

Теперь проверьте, что наша система безопасности возвращает 401 для запросов, не прошедших проверку подлинности:

Затем мы проверим, что Весенняя сессия управляет нашим токеном аутентификации:

Во-первых, наш тест подтверждает, что наш запрос был успешным с использованием учетных данных проверки подлинности администратора.

Наконец, мы делаем еще один запрос, используя файл cookie сеанса, и подтверждаем, что мы вышли из системы. Это подтверждает, что Весенняя сессия управляет нашими сессиями.

7. Заключение

Spring Session – это мощный инструмент для управления HTTP-сессиями. Теперь, когда наше хранилище сеансов упрощено до класса конфигурации и нескольких зависимостей Maven, мы можем подключить несколько приложений к одному и тому же экземпляру Redis и обмениваться информацией об аутентификации.

Источник

Управление сессиями с использованием Spring Session с JDBC DataStore

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

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

Давайте кратко рассмотрим эти подходы.

1. Одноузловой сервер

Если ваше приложение не является критически важной услугой для вашего бизнеса, одновременно не будет слишком много пользователей, и допускается некоторое время простоя, тогда мы можем развернуть Single Node Server, как показано ниже:


В этой модели для каждого клиента браузера на сервере создается объект сеанса ( HttpSession в случае Java), а SESSION_ID будет установлен в браузере как cookie для идентификации объекта сеанса. Но это развертывание с одним серверным узлом неприемлемо для большинства приложений, потому что, если сервер выйдет из строя, служба будет полностью недоступна.

2. Многоузловой сервер с липкими сессиями

Чтобы сделать наше приложение доступным и обслуживать большее количество пользователей, мы можем иметь несколько узлов сервера за балансировщиком нагрузки. В подходе Sticky Sessions мы настраиваем наш балансировщик нагрузки для маршрутизации всех запросов от одного и того же клиента к одному и тому же узлу.

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

3. Многоузловой сервер с репликацией сеанса

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

Но для репликации сеансов требуется более качественная аппаратная поддержка и некоторые конфигурации, специфичные для сервера.

4. Многоузловой сервер с данными сеанса в постоянном хранилище данных

В этой модели пользовательские данные сеанса не будут храниться в памяти сервера, вместо этого они будут сохранены в хранилище данных и ассоциированы с SESSION_ID.

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

Это где Весенняя Сессия входит в картину.

Весенняя сессия

Spring Session — это реализация подхода 4, который заключается в хранении данных сеанса в постоянном хранилище данных. Spring Session поддерживает несколько хранилищ данных, таких как RDBMS, Redis, HazelCast, MongoDB и т. Д. Для прозрачного сохранения данных сеанса использования. Как обычно, использование Spring Session с Spring Boot так же просто, как добавление зависимости и настройка нескольких свойств.
Давайте посмотрим, как мы можем использовать Spring Session с внутренним хранилищем JDBC в приложении Spring Boot.

Источник

Развивающий портал