Статьи

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

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

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

  1. Сервер с одним узлом
  2. Многоузловой сервер с балансировкой нагрузки и Sticky-сессиями
  3. Многоузловой сервер с балансировкой нагрузки и репликацией сеансов
  4. Многоузловой сервер с балансировщиком нагрузки и данными сеанса в постоянном хранилище данных

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

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.

https://github.com/sivaprasadreddy/spring-session-samples

Шаг 1. Создание приложения Spring Boot

Создайте приложение SpringBoot, используя самую последнюю версию (на момент написания — 2.0.0.RC1) с помощью Web , Thymeleaf , JPA , H2 , Session starts.
По умолчанию стартер Session добавит org.springframework.session: зависимость spring-session-core , давайте изменим ее на spring-session- jdbc, так как мы собираемся использовать бэкэнд JDBC.

1
2
3
4
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-jdbc</artifactId>
</dependency>

Шаг 2. Настройка свойств сеанса Spring

Мы можем настроить тип внутреннего хранилища данных Spring Session, используя свойство spring.session.store-type в application.properties .

1
spring.session.store-type=jdbc

Поскольку мы используем базу данных H2 In-Memory, Spring Session создает следующие таблицы, необходимые для автоматического сохранения данных сеанса из скрипта spring-session- jdbc -2.0.1.RELEASE.jar! / Org / springframework / session / jdbc / schema -h2.sql .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE SPRING_SESSION (
    PRIMARY_ID CHAR(36) NOT NULL,
    SESSION_ID CHAR(36) NOT NULL,
    CREATION_TIME BIGINT NOT NULL,
    LAST_ACCESS_TIME BIGINT NOT NULL,
    MAX_INACTIVE_INTERVAL INT NOT NULL,
    EXPIRY_TIME BIGINT NOT NULL,
    PRINCIPAL_NAME VARCHAR(100),
    CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);
  
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
  
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
    SESSION_PRIMARY_ID CHAR(36) NOT NULL,
    ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
    ATTRIBUTE_BYTES LONGVARBINARY NOT NULL,
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);
  
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);

Но если мы собираемся использовать другие СУБД, такие как MySQL, мы можем настроить следующим образом:

Добавьте MySQL maven зависимость.

1
2
3
4
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

Настройте свойства источника данных для MySQL:

1
2
3
4
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root
spring.datasource.password=admin

Включите создание таблиц Spring Session с помощью свойства spring.session.jdbc.initialize-schema .

1
spring.session.jdbc.initialize-schema=always

С помощью этого свойства Spring Session будет пытаться создавать таблицы, используя скрипт «classpath: org / springframework / session / jdbc / schema — @@ platform @@. Sql» , поэтому в нашем случае он будет использовать schema-mysql.sql .

Шаг 3: Добавить данные в HttpSession

Теперь создайте простую форму в src / main / resources / templates / index.html.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring Session + JDBC Demo</title>
</head>
<body>
  
<div>
  
<form th:action="@{/messages}" method="post">
    <textarea name="msg" cols="40" rows="4"></textarea>
    <input type="submit" value="Save"/>
</form>
  
</div>
  
<div>
      
<h2>Messages</h2>
      
<ul th:each="m : ${messages}">
          
  <li th:text="${m}">msg</li>
  
</ul>
  
</div>
  
</body>
</html>

Давайте реализуем контроллер для добавления сообщений в HttpSession и их отображения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
  
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
  
@Controller
public class MessagesController
{
  
    @GetMapping("/")
    public String index(Model model, HttpSession session) {
        List<String> msgs = (List<String>) session.getAttribute("MY_MESSAGES");
        if(msgs == null) {
            msgs = new ArrayList<>();
        }
        model.addAttribute("messages", msgs);
        return "index";
    }
  
    @PostMapping("/messages")
    public String saveMessage(@RequestParam("msg") String msg, HttpServletRequest request)
    {
        List<String> msgs = (List<String>) request.getSession().getAttribute("MY_MESSAGES");
        if(msgs == null) {
            msgs = new ArrayList<>();
            request.getSession().setAttribute("MY_MESSAGES", msgs);
        }
        msgs.add(msg);
        return "redirect:/";
    }
}

Теперь вы можете запустить приложение и добавить некоторые сообщения в HttpSession и увидеть строки в таблицах SPRING_SESSION , SPRING_SESSION_ATTRIBUTES . По умолчанию Spring Session преобразует объекты, которые мы пытаемся добавить в HttpSession, в ByteArray и сохраняет его в таблице.

Весенняя сессия с Spring Security

Spring Session легко интегрируется с Spring Security благодаря автоматической настройке SpringBoot.
Давайте добавим Spring Security в наше приложение.

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Добавьте учетные данные пользователя по умолчанию в application.properties следующим образом:

1
2
spring.security.user.name=admin
spring.security.user.password=secret

Теперь, если вы попытаетесь получить доступ к http: // localhost: 8080 /, вы будете перенаправлены на автоматически сгенерированную страницу входа.
Как только вы войдете в систему и увидите данные в таблице SPRING_SESSION, вы увидите, что имя пользователя для входа в систему хранится в столбце PRINCIPAL_NAME .

Как работает Spring Session?

Spring Session предоставляет реализации для HttpServletRequest и HttpSession, которые являются SessionRepositoryRequestWrapper и HttpSessionWrapper . Spring Session предоставляет SessionRepositoryFilter для перехвата всех запросов и переноса HttpServletRequest в SessionRepositoryRequestWrapper .

В SessionRepositoryRequestWrapper.getSession (boolean) переопределяется для возврата объекта HttpSessionWrapper вместо серверной реализации HttpSession по умолчанию. HttpSessionWrapper использует SessionRepository для сохранения информации о сеансе в хранилище данных.

Интерфейс SessionRepository имеет различные методы для управления сессиями.

01
02
03
04
05
06
07
08
09
10
public interface SessionRepository<S extends Session>
{
    S createSession();
  
    void save(S session);
  
    S findById(String id);
  
    void deleteById(String id);
}

Этот интерфейс SessionRepository реализуется различными классами в зависимости от типа используемого нами бэкэнда. В нашем случае мы используем JdbcOperationsSessionRepository, предоставленный spring-session-jdbc .

Вывод

Как вы, возможно, уже заметили, мы можем эффективно управлять пользовательскими сеансами, используя Spring Session с минимальной конфигурацией благодаря автоматической настройке Spring Boot. Если по какой-либо причине мы хотим изменить бэкэнд с JDBC на Redis или Hazelcast и т. Д., Это всего лишь простое изменение конфигурации, поскольку мы не зависим напрямую от классов Spring Session.

Вы можете найти исходный код этой статьи по адресу https://github.com/sivaprasadreddy/spring-session-samples

Опубликовано на Java Code Geeks с разрешения Сивы Редди, партнера нашей программы JCG . См. Оригинальную статью здесь: Управление сессиями с использованием Spring Session с JDBC DataStore.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.