当前位置: 首页 > 科技观察

集群部署,SpringSecurity如何处理Session共享?

时间:2023-03-21 19:27:43 科技观察

大家都在说SpringSecurity如何像QQ一样自动踢出登录用户,但是我们是基于单体应用的。如果我们的项目部署在集群中,如何解决这个问题呢?今天我们就来看看集群部署,SpringSecurity是如何处理会话并发的。1、集群session解决方案在传统的单服务架构中,一般来说只有一台服务器,所以不存在session共享的问题。但是在分布式/集群项目中,session共享是一个必须要面对的问题。先来看一个简单的架构图:在这样的架构中,会出现一些单个服务不存在的问题。比如客户端发起一个请求。请求到达Nginx后,由Nginx转发给TomcatA,然后在TomcatA的session中保存一段数据,下次有请求来时,再将请求转发给TomcatB,此时,再次在session中获取数据,发现没有之前的数据了。1.1Session共享解决这类问题,目前主流的方案是将各个服务之间需要共享的数据保存在一个公共场所(主流方案是Redis):当所有Tomcat都需要向Session写入数据时,都写入Redis,所有Tomcat需要读取数据时,都从Redis读取。这样不同的服务就可以使用同一个Session数据。这样的方案可以由开发者手动实现,即手动将数据存入Redis,手动从Redis读取数据,相当于使用一些Redis客户端工具来实现这样的功能。毫无疑问,人工实现的工作量还是蛮大的。一个简化的方案是使用SpringSession来实现这个功能。SpringSession使用Spring中的代理过滤器拦截所有Session操作,自动同步数据到Redis,或者自动从Redis中读取数据。对于开发者来说,所有与Session同步相关的操作都是透明的。开发人员使用SpringSession。配置完成后,具体使用方法和使用普通Session一样。1.2sessioncopysessioncopy是不使用redis直接在Tomcat之间复制session数据,但是这种方式效率有点低。如果TomcatA、B、C中任何一个的session发生变化,都需要复制到其他Tomcat上。事实上,如果集群中的服务器数量特别多,这种方式不仅效率低,而且有严重的问题延误。所以这种方案一般算是理解了。1.3Stickysession所谓stickysession就是把同一个IP发出的请求通过Nginx路由到同一个Tomcat,这样就不需要session共享和同步了。这是一种方式,但在某些极端情况下,可能会造成负载不平衡(因为大多数情况下,很多人使用同一个公网IP)。因此,Session共享成为了解决这个问题的主流方案。2.Session分享2.1创建项目首先创建一个SpringBoot项目,引入Web、SpringSession、SpringSecurity和Redis:创建成功后pom.xml文件如下:org.springframework.bootspring-boot-starter-data-redisorg.springframework.bootspring-boot-starter-安全/dependency>org.springframework.bootspring-boot-starter-weborg.springframework.sessionspring-session-data-redis2.2配置spring.redis.password=123spring.redis.port=6379spring.redis.host=127.0.0.1spring.security.user.name=javaboyspring.security.user.password=123server.port=8080配置Redis的基本信息;为了简化SpringSecurity,我会直接在应用中配置用户名和密码。properties,最后配置项目端口号2.3配置完成后,就可以使用SpringSession了,其实就是使用普通的HttpSession,其他Session同步到Redis等操作,框架已经自动帮你完成了:@RestControllerpublicclassHelloController{@Value("${server.port}")Integerport;@GetMapping("/set")publicStringset(HttpSessionsession){session.setAttribute("user","javaboy");returnString.valueOf(port);}@GetMapping("/get")publicStringget(HttpSessionsession){returnsession.getAttribute("user")+":"+port;}}考虑到SpringBoot会在集群中启动一段时间,为了获取每个请求提供的服务通过SpringBoot每次请求都需要返回当前服务的端口号,所以这里我注入了server.port。接下来,工程打包:打包后,启动工程的两个实例:java-jarsession-4-0.0.1-SNAPSHOT.jar--server.port=8080java-jarsession-4-0.0.1-SNAPSHOT.jar--server.port=8081然后访问localhost:8080/set在8080服务的Session中保存一个变量。第一次访问时,会自动跳转到登录页面,输入用户名和密码即可登录。访问成功后,数据已经自动同步到Redis:然后,调用localhost:8081/get接口获取8080服务session中的数据:此时session共享配置完成。我们已经看到了会话共享的效果。2.4安全配置Session共享实现了,但是我们发现了一个新问题,SpringBoot+Vue前后端分离的时候,如何踢掉已经登录的用户?我们在本文中配置的会话并发管理是无效的。也就是说,如果我添加以下配置:protectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().anyRequest()....sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);}现在这个配置不起作用但是,用户仍然可以同时登录多个浏览器。这是怎么回事?在这篇文章中,我们提到了会话注册表的维护默认是由SessionRegistryImpl来维护的,而SessionRegistryImpl的维护是基于内存的。现在虽然启用了SpringSession+Redis进行Session共享,但是SessionRegistryImpl仍然是基于内存维护的,所以需要修改SessionRegistryImpl的实现逻辑。修改方式也很简单,实际上SpringSession为我们提供了对应的实现类SpringSessionBackedSessionRegistry,具体配置如下:@ConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredFindByIndexNameSessionRepositorysessionRepository;@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().anyRequest()....sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true).sessionRegistry(sessionRegistry());}@BeanSpringSessionBackedSessionRegistrysessionRegistry(){returnnewSpringSessionBackedSessionRegistry(sessionRepository);}}我们只需要在这里提供一个SpringSessionBackedSessionRegistry的实例,配置到sessionManagement.以后session并发数据的维护将由SpringSessionBackedSessionRegistry来完成,而不是SessionRegistryImpl。这样我们配置的session并发就生效了。在集群环境下,用户只能在一台设备上登录。为了让我们的案例看起来更完美,下面引入Nginx来实现服务实例的自动切换。3.引入Nginx非常简单。进入Nginx安装目录的conf目录(默认为/usr/local/nginx/conf),编辑nginx.conf文件:本次配置中:upstream表示配置上游服务器javaboy.org表示的名字服务器集群,可随意命名。上游配置有单独的服务。weight表示服务的权重,表示有多少请求会从Nginx转发到该服务。location中的proxy_pass表示请求转发地址,/表示拦截所有请求,转发给刚才配置的服务集群。proxy_redirect表示当有重定向请求时,nginx自动修正响应头数据(默认是Tomcat返回重定向,此时的重定向地址为Tomcat的地址,我们需要修改为Nginx的地址).配置完成后,将本地的SpringBoot打包jar上传到Linux,然后在Linux上启动两个SpringBoot实例:nohupjava-jarsession-4-0.0.1-SNAPSHOT.jar--server.port=8080&nohupjava-jarsession-4-0.0.1-SNAPSHOT.jar--server.port=8081&其中nohup表示当终端关闭时,SpringBoot不要停止运行&表示让SpringBoot在后台启动配置完成后,重启Nginx:/usr/local/nginx/sbin/nginx-sreloadNginx启动成功后,我们先手动清除Redis上的数据,然后访问192.168.66.128/set保存session中的数据。这个请求会先到达Nginx,然后Nginx会转发给某个SpringBoot例子:如上,8081端口的SpringBoot处理了/set请求,然后访问/get请求:可以看到/get请求由端口8080的服务处理。4.小结本文主要介绍了SpringSession的使用,同时也涉及到一些Nginx的使用。这篇文章虽然很长,但是SpringSession的配置其实没什么,涉及的配置也很简单。如果你没有在SSM架构中使用过SpringSession,你可能无法理解我们在SpringBoot中使用SpringSession有多么方便,因为在SSM架构中,SpringSession的使用需要在三个地方进行配置,一就是在web.xml中配置代理过滤器,然后在Spring容器中配置Redis,最后配置SpringSession。步骤还是有点繁琐,SpringBoot直接省去了我们这些繁琐的步骤!好了,本文就这些了,本文我已经将相关案例上传到GitHub,大家可以自行下载:https://github.com/lenve/spring-security-samples