在Web应用程序中,用户会话管理对于管理用户状态至关重要。 在本文中,我们将学习在集群环境中管理用户会话所遵循的方法,以及如何使用Spring Session以更加简单和可扩展的方式实现它。
通常在生产环境中,我们将有多个服务器节点,并在它们前面有一个负载平衡器,并且所有客户端流量都将通过负载平衡器到达其中一个服务器节点。 因此,我们需要某种机制来使用户会话数据可用于集群环境中的每个客户端。
传统上,我们一直使用以下技术来管理会话:
- 单节点服务器
- 具有负载均衡器和粘性会话的多节点服务器
- 具有负载均衡器和会话复制的多节点服务器
- 持久性数据存储中具有负载均衡器和会话数据的多节点服务器
让我们简要地看一下这些方法。
1.单节点服务器
如果您的应用程序不是对企业至关重要的服务,那么并发用户不会太多,并且可以接受一些停机时间,那么我们可以进行单节点服务器部署,如下所示:
在此模型中,对于每个浏览器客户端,将在服务器上创建会话对象(对于Java,则为HttpSession ),并且SESSION_ID将被设置为浏览器上的cookie,以标识会话对象。 但是对于大多数应用程序来说,这种单服务器节点部署是不可接受的,因为如果服务器关闭,服务将完全关闭。
2.具有粘性会话的多节点服务器
为了使我们的应用程序高度可用并满足更多用户,我们可以在负载均衡器后面有多个服务器节点。 在“粘性会话”方法中,我们将负载均衡器配置为将所有请求从同一客户端路由到同一节点。
在此模型中,将在服务器节点中的任何一个上创建用户会话,并将来自该客户端的所有其他请求发送到该相同节点。 但是这种方法的问题是,如果服务器节点出现故障,那么该服务器上的所有用户会话都将消失。
3.具有会话复制的多节点服务器
在此模型中,用户会话数据将在所有服务器节点上复制,以便可以将任何请求路由到任何服务器节点。 即使一个节点发生故障,客户端请求也可以由另一节点服务。
但是会话复制需要更好的硬件支持,并涉及某些服务器特定的配置。
4.具有持久数据存储区中的会话数据的多节点服务器
在此模型中,用户会话数据将不保存在服务器的内存中,而是将其持久保存到数据存储中并将其与SESSION_ID关联。
此解决方案将独立于服务器,但是每当用户向其会话中添加一些信息时,我们可能都需要编写自定义代码以将会话数据透明地存储在Persistent数据存储区中。
这是Spring Session出现的地方。
Spring会议
Spring Session是方法4的实现,它是将会话数据存储在持久数据存储区中。 Spring Session支持RDBMS,Redis,HazelCast,MongoDB等多个数据存储,以透明地保存使用会话数据。 与往常一样,将Spring Session与Spring Boot一起使用就像添加依赖项和配置少量属性一样简单。
让我们看看如何在Spring Boot应用程序中将Spring Session与JDBC后端存储一起使用。
https://github.com/sivaprasadreddy/spring-session-samples
步骤1:创建Spring Boot应用程序
使用具有Web , Thymeleaf , JPA , H2和Session starters的最新版本(撰写时为2.0.0.RC1 )创建SpringBoot应用程序。
默认情况下,Session入门程序将添加org.springframework.session:spring-session-core依赖项 ,让我们在使用JDBC后端时将其更改为spring-session- jdbc 。
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-jdbc</artifactId>
</dependency>
步骤2:配置Spring Session属性
我们可以在application.properties使用spring.session.store类型的属性配置Spring会议后端数据存储的类型。
spring.session.store-type=jdbc
当我们使用H2内存数据库时,Spring Session将创建以下表,这些表是通过脚本spring-session- jdbc -2.0.1.RELEASE.jar!/ org / springframework / session / jdbc / schema自动存储会话数据所需的。 -h2.sql 。
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);
但是,如果我们要使用其他RDBMS(例如MySQL),则可以进行如下配置:
添加MySQL Maven依赖项。
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
为MySQL配置数据源属性:
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.jdbc.initialize-schema属性启用Spring Session表创建。
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中创建一个简单的表单。
<!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>
让我们实现一个Controller,以将消息添加到HttpSession并显示它们。
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 SecuritySpring会议
由于SpringBoot的自动配置, Spring Session与Spring Security无缝集成。
让我们将Spring Security添加到我们的应用程序中。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
在application.properties中添加默认用户凭据,如下所示:
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接口具有多种管理会话的方法。
public interface SessionRepository<S extends Session>
{S createSession();void save(S session);S findById(String id);void deleteById(String id);
}
这个SessionRepository接口由各种类根据我们使用的后端类型实现。 在本例中,我们使用的是spring-session-jdbc提供的JdbcOperationsSessionRepository 。
结论
如您可能已经观察到的,由于Spring Boot的自动配置,我们可以通过使用Spring Session进行非常少的配置来有效地管理用户会话。 如果由于某种原因我们想将后端从JDBC更改为Redis或Hazelcast等,那只是简单的配置更改,因为我们不直接依赖于任何Spring Session类。
您可以在https://github.com/sivaprasadreddy/spring-session-samples中找到本文的源代码。
翻译自: https://www.javacodegeeks.com/2018/02/session-management-using-spring-session-jdbc-datastore.html