本文主要介绍“Spring Security如何处理会话共享”。在日常操作中,我相信很多人对Spring Security如何处理Session共享有疑问。边肖查阅了各种资料,整理出简单易用的操作方法,希望能帮你解答“Spring Security如何处理Session共享”的疑惑!接下来,请和边肖一起学习!
1.集群会话方案
在传统的单一服务架构中,一般来说只有一个服务器,所以不存在Session共享的问题。然而,在分布式/集群项目中,会话共享是必须面对的问题。首先,看一个简单的架构图:
在这种架构下,会出现一些单一服务不存在的问题,比如客户端发起请求,请求到达Nginx后,由Nginx转发给Tomcat A,然后在Tomcat A上的session中保存一段数据,下一次向Tomcat B发送请求,此时发现Session中没有之前的数据。
1.1 session 共享
目前这类问题的主流解决方案是将需要在各种服务之间共享的数据保存在公共场所(主流解决方案是Redis):
当所有Tomcat需要将数据写入Session时,它们都写入Redis,当所有Tomcat需要读取数据时,它们都从Redis读取。这样,不同的服务可以使用相同的会话数据。
这个方案可以由开发人员手动实现,即在Redis中手动存储数据,从Redis中手动读取数据,相当于使用一些Redis客户端工具来实现这个功能。毫无疑问,手工实现的工作量还是相当大的。
一个简化的解决方案是使用Spring Session来实现这个功能。Spring Session是使用Spring中的代理过滤器拦截所有会话操作,自动将数据同步到Redis,或者自动从Redis读取数据。
对于开发人员来说,所有关于会话同步的操作都是透明的。开发人员使用春季会议。一旦配置完成,具体用法就像使用普通会话一样。
1.2 session 拷贝
会话复制是不使用redis直接在Tomcats之间复制会话数据,但是这种方法效率有点低。Tomcat A、B和C的任何会话都已更改,需要复制到其他Tomcat。如果集群中的服务器数量特别多,这种方法不仅效率低,而且延迟严重。
因此,这种方案一般可以视为理解。
1.3 粘滞会话
所谓粘性会话,就是把来自同一个IP的请求通过Nginx路由到同一个Tomcat,这样就不需要会话共享和同步了。这是一种解决方案,但在某些极端情况下,可能会导致负载不平衡(因为大多数情况下,许多人使用相同的公共IP)。
因此,会话共享成为解决这一问题的主流方案。
2.Session共享
2.1 创建工程
首先,创建一个Spring Boot项目,介绍Web、春季会议、春季安全和Redis:
成功创建后,pom.xml文件如下所示:
dependencygroupidgor . spring framework . boot/group idartifactidspring-boot-starter-data-redis/artifactId/dependency dependency cynbs
p; <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
2.2 配置
spring.redis.password=123 spring.redis.port=6379 spring.redis.host=127.0.0.1 spring.security.user.name=javaboy spring.security.user.password=123 server.port=8080
配置一下 Redis 的基本信息;Spring Security 为了简化,我就将用户名密码直接配置在 application.properties 中了,最后再配置一下项目端口号。
2.3 使用
配置完成后 ,就可以使用 Spring Session 了,其实就是使用普通的 HttpSession ,其他的 Session 同步到 Redis 等操作,框架已经自动帮你完成了:
@RestController public class HelloController { @Value("${server.port}") Integer port; @GetMapping("/set") public String set(HttpSession session) { session.setAttribute("user", "javaboy"); return String.valueOf(port); } @GetMapping("/get") public String get(HttpSession session) { return session.getAttribute("user") + ":" + port; } }
考虑到一会 Spring Boot 将以集群的方式启动 ,为了获取每一个请求到底是哪一个 Spring Boot 提供的服务,需要在每次请求时返回当前服务的端口号,因此这里我注入了 server.port 。
接下来 ,项目打包:
打包之后,启动项目的两个实例:
java -jar session-4-0.0.1-SNAPSHOT.jar --server.port=8080 java -jar session-4-0.0.1-SNAPSHOT.jar --server.port=8081
然后先访问 localhost:8080/set 向 8080 这个服务的 Session 中保存一个变量,第一次访问时会自动跳转到登录页面,输入用户名密码进行登录即可。访问成功后,数据就已经自动同步到 Redis 中 了 :
然后,再调用 localhost:8081/get 接口,就可以获取到 8080 服务的 session 中的数据:
此时关于 session 共享的配置就已经全部完成了,session 共享的效果我们已经看到了。
2.4 Security 配置
Session 共享已经实现了,但是我们发现新的问题,在Spring Boot + Vue 前后端分离项目,如何踢掉已登录用户?一文中我们配置的 session 并发管理失效了。
也就是说,如果我添加了如下配置:
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest() ... .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(true); }
现在这个配置不起作用,用户依然可以在多个浏览器上同时登录。
这是怎么回事呢?
在该文中,我们提到,会话注册表的维护默认是由 SessionRegistryImpl 来维护的,而 SessionRegistryImpl 的维护就是基于内存的维护。现在我们虽然启用了 Spring Session+Redis 做 Session 共享,但是 SessionRegistryImpl 依然是基于内存来维护的,所以我们要修改 SessionRegistryImpl 的实现逻辑。
修改方式也很简单,实际上 Spring Session 为我们提供了对应的实现类 SpringSessionBackedSessionRegistry,具体配置如下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired FindByIndexNameSessionRepository sessionRepository; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest() ... .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(true) .sessionRegistry(sessionRegistry()); } @Bean SpringSessionBackedSessionRegistry sessionRegistry() { return new SpringSessionBackedSessionRegistry(sessionRepository); } }
我们在这里只需要提供一个 SpringSessionBackedSessionRegistry 的实例,并且将其配置到 sessionManagement 中去即可。以后,session 并发数据的维护将由 SpringSessionBackedSessionRegistry 来完成,而不是 SessionRegistryImpl,如此,我们关于 session 并发的配置就生效了,在集群环境下,用户也只可以在一台设备上登录。
为了让我们的案例看起更完美一些,接下来我们来引入 Nginx ,实现服务实例自动切换。
3.引入 Nginx
很简单,进入 Nginx 的安装目录的 conf 目录下(默认是在/usr/local/nginx/conf),编辑 nginx.conf 文件:
在这段配置中:
-
upstream 表示配置上游服务器
-
javaboy.org 表示服务器集群的名字,这个可以随意取名字
-
upstream 里边配置的是一个个的单独服务
-
weight 表示服务的权重,意味者将有多少比例的请求从 Nginx 上转发到该服务上
-
location 中的 proxy_pass 表示请求转发的地址,/ 表示拦截到所有的请求,转发转发到刚刚配置好的服务集群中
-
proxy_redirect 表示设置当发生重定向请求时,nginx 自动修正响应头数据(默认是 Tomcat 返回重定向,此时重定向的地址是 Tomcat 的地址,我们需要将之修改使之成为 Nginx 的地址)。
配置完成后,将本地的 Spring Boot 打包好的 jar 上传到 Linux ,然后在 Linux 上分别启动两个 Spring Boot 实例:
nohup java -jar session-4-0.0.1-SNAPSHOT.jar --server.port=8080 & nohup java -jar session-4-0.0.1-SNAPSHOT.jar --server.port=8081 &
其中
-
nohup 表示当终端关闭时,Spring Boot 不要停止运行
-
& 表示让 Spring Boot 在后台启动
配置完成后,重启 Nginx:
/usr/local/nginx/sbin/nginx -s reload
Nginx 启动成功后,我们首先手动清除 Redis 上的数据,然后访问192.168.66.128/set 表示向 session 中保存数据,这个请求首先会到达 Nginx 上,再由 Nginx 转发给某一个 Spring Boot 实例:
如上,表示端口为 8081 的 Spring Boot 处理了这个 /set 请求,再访问 /get 请求:
可以看到,/get 请求是被端口为 8080 的服务所处理的。
到此,关于“Spring Security怎么处理Session 共享”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/90607.html