Spring Cloud Gateway中session共享

article/2025/9/17 8:26:32

Spring Cloud Gateway中session共享

背景

在进行zuul切换到gateway时,需要重新实现session共享,本文主要分享一下自己实现的方案。

zuul中的session共享

在zuul中,是通过spring-session-data-redis这个组件,将session的信息存放到redis中实现的session共享。这次也简单说明下如何实现以及一些注意的点。

首先在网关zuul以及所有的微服务中添加spring-session-data-redis依赖:

<!-- session共享 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>

之后添加redis配置信息:

spring:redis:host: localhostport: 6379

添加EnableRedisHttpSession注解:

/*** 指定flushMode为IMMEDIATE 表示立即将session写入redis** @author yuanzhihao* @since 2022/5/8*/
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)
@Configuration
public class RedisSessionConfig {
}

在网关zuul工程中,路由跳转到微服务时,需要添加sensitiveHeaders,设置为空,表示将敏感信息透传到下游微服务,这边需要将cookie的信息传下去,session共享保存到redis里面需要用到:

zuul:routes:portal:path: /portal/**sensitiveHeaders: # 将敏感信息传到下游服务serviceId: portal

指定server.servlet.context-path路径:

server.servlet.context-path=/gateway

zuul测试工程

在我的代码库中,我提交了一个简单的demo,主要有四个工程,分别是网关zuul、主页portal、两个客户端client-1、server-1。

网关zuul中添加路由信息:

spring:application:name: zuulredis:host: localhostport: 6379
server:servlet:context-path: /gateway
zuul:routes:portal:path: /portal/**sensitiveHeaders:serviceId: portalclient-1:path: /client1/**sensitiveHeaders:serviceId: eureka-client1server-1:path: /server1/**sensitiveHeaders:serviceId: eureka-server1

添加登录过滤器,对所有的请求进行拦截,对于没有登录的请求会自动跳转到登录页面:

/*** 登录过滤器** @author yuanzhihao* @since 2022/5/8*/
@Component
@Slf4j
public class LoginFilter extends ZuulFilter {private static final List<String> white_List = Arrays.asList("/login", "/logout");@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return -1;}@Overridepublic boolean shouldFilter() {HttpServletRequest request = RequestContext.getCurrentContext().getRequest();String requestURI = request.getRequestURI();for (String uri : white_List) {if (requestURI.endsWith(uri)) {return false;}}return true;}@SneakyThrows@Overridepublic Object run() throws ZuulException {RequestContext currentContext = RequestContext.getCurrentContext();HttpServletRequest request = currentContext.getRequest();HttpSession session = request.getSession();UserInfo userInfo = (UserInfo) session.getAttribute("userInfo");if (userInfo == null) {HttpServletResponse response = currentContext.getResponse();response.sendRedirect("/gateway/portal/login");}return null;}
}

portal中简单实现了登录逻辑:

/*** @author yuanzhihao* @since 2022/5/8*/
@Controller
public class LoginController {@GetMapping(value = "/login")public String login(HttpServletRequest request, HashMap<String, Object> map) {UserInfo userInfo = (UserInfo) request.getSession().getAttribute("userInfo");if (userInfo != null) {map.put("userInfo", userInfo);return "index";}return "login";}@PostMapping("/login")public String login(UserInfo userInfo, HashMap<String, Object> map, HttpServletRequest request) {// 设置sessionrequest.getSession().setAttribute("userInfo", userInfo);map.put("userInfo", userInfo);return "index";}@GetMapping("/logout")public String logout(HttpServletRequest request) {request.getSession().invalidate();return "logout";}
}

在客户端client-1和server-1中可以请求到当前session中的用户信息:

@GetMapping("/hello")
public String hello(HttpServletRequest request) {UserInfo userInfo = (UserInfo) request.getSession().getAttribute("userInfo");return "Client1 Hello " + userInfo.getUsername();
}

未登录时,通过网关访问其他微服务页面会重定向:
在这里插入图片描述

登录后,可以正常访问,并且在其他微服务中可以获取到session中的用户信息:
在这里插入图片描述
client-1:
在这里插入图片描述
server-1:
在这里插入图片描述

spring cloud gateway中session共享

在spring cloud gateway中,和zuul有一些区别,下面整理了这些区别以及要如何修改。

httpSession和webSession

首先spring cloud gateway是基于webflux,是非阻塞的,zuul是基于servlet的,是阻塞的(这部分差异大家可以自行了解一下,我也不是很熟~)。他们的session是两种实现,在zuul中是httpSession,而到了gateway中是webSession。

在gateway中需要将EnableRedisHttpSession注解换成EnableRedisWebSession:

/*** 指定saveMode为ALWAYS 功能和flushMode类似** @author yuanzhihao* @since 2022/5/6*/
@EnableRedisWebSession(saveMode = SaveMode.ALWAYS)
@Configuration
@Slf4j
public class RedisSessionConfig {}

同时需要覆盖webSession中读取sessionId的写法,将SESSION信息进行base64解码,默认实现中是没有base64解码的,sessionId传到下游时不一致,会导致session不共享:

// 覆盖默认实现
@Bean
public WebSessionIdResolver webSessionIdResolver() {return new CustomWebSessionIdResolver();
}private static class CustomWebSessionIdResolver extends CookieWebSessionIdResolver {// 重写resolve方法 对SESSION进行base64解码@Overridepublic List<String> resolveSessionIds(ServerWebExchange exchange) {MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies();// 获取SESSIONList<HttpCookie> cookies = cookieMap.get(getCookieName());if (cookies == null) {return Collections.emptyList();}return cookies.stream().map(HttpCookie::getValue).map(this::base64Decode).collect(Collectors.toList());}private String base64Decode(String base64Value) {try {byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);return new String(decodedCookieBytes);} catch (Exception ex) {log.debug("Unable to Base64 decode value: " + base64Value);return null;}}
}

这边可以参考下具体的源码。httpSession在读取的时候,会进行解码,具体方法地址org.springframework.session.web.http.DefaultCookieSerializer#readCookieValues
在这里插入图片描述

添加context-path

spring-cloud-gateway不是基于servlet的,所以设置了server.servlet.context-path属性并不生效,这边参考其他人的方案使用了另一种方法添加了context-path。使用StripPrefix的方式。StripPrefix的参数表示在进行路由转发到下游服务之前,剥离掉请求中StripPrefix参数个数的路径参数。比如StripPrefix为2,像网关发起的请求是/gateway/client1/name,转发到下游时,请求路径会变成/name,这样就添加完成了context-path。

具体路由的配置信息如下:

spring:application:name: gatewaycloud:gateway:routes:- id: client1uri: lb://eureka-client1predicates:- Path=/gateway/client1/**filters:- StripPrefix=2- id: server1Sessionuri: lb://eureka-server1predicates:- Path=/gateway/server1/**filters:- StripPrefix=2- id: portaluri: lb://portalpredicates:- Path=/gateway/portal/**filters:- StripPrefix=2

到现在差不多就完成了gateway的session共享。

gateway测试工程

这边测试工程和上面一致,只是将网关换成了gateway。

我们在gateway中添加一个登录过滤器拦截所有的请求,对于没有登录的请求跳转到登录页面:

/*** 登录过滤器** @author yuanzhihao* @since 2022/5/6*/
@Component
@Slf4j
public class LoginGlobalFilter implements GlobalFilter, Ordered {private static final List<String> white_List = Arrays.asList("/login", "/logout");// 登录地址private static final String PORTAL_URL = "https://localhost:7885/gateway/portal/login";@SneakyThrows@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {System.err.println("login filter starter");// 判断是否登录AtomicBoolean isLogin = new AtomicBoolean(false);exchange.getSession().subscribe(webSession -> {UserInfo userInfo = webSession.getAttribute("userInfo");System.err.println("userInfo is " + userInfo);if (userInfo != null) {isLogin.set(true);}});// 这边添加一个延时, 等待获取到sessionThread.sleep(200);// url白名单String path = exchange.getRequest().getURI().getPath();boolean isWhiteUrl = white_List.stream().anyMatch(path::endsWith);// 登录状态或者在url白名单中 放行if (isLogin.get() || isWhiteUrl) {return chain.filter(exchange);}ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.SEE_OTHER);response.getHeaders().set(HttpHeaders.LOCATION, PORTAL_URL);response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");return response.setComplete();}@Overridepublic int getOrder() {return -1;}
}

这边我添加了一个200ms的睡眠,因为测试验证的时候,当请求进入这个过滤器时,获取到的webSession是空,导致逻辑异常。猜测是由于spring-cloud-gateway是基于netty实现的非阻塞IO,所以获取session有一定的延迟,所有添加了一个sleep阻塞。后续会考虑修改。

之前也尝试过使用block()方法修改为阻塞的,但是抛异常了,具体原因没有分析出来。

这边通过gateway访问和zuul的结果一致:
在这里插入图片描述
在其他微服务中也可以获取到session中的用户信息:
在这里插入图片描述在这里插入图片描述

结语

以上就是Spring Cloud Gateway中session共享的方案,在网络上相关的文章很少,如果大家有其他不错的方案,希望也可以分享一下。

参考地址:

https://stackoverflow.com/questions/50325674/spring-cloud-gateway-api-context-path-on-routes-not-working

https://github.com/spring-cloud/spring-cloud-gateway/issues/1920

https://www.shuzhiduo.com/A/QV5Zg2o2dy/

代码地址:https://github.com/yzh19961031/SpringCloudDemo

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦


http://chatgpt.dhexx.cn/article/nbNktqCG.shtml

相关文章

Nginx - session共享

1.负载均衡 session问题 通过负载均衡&#xff0c;我们可以把请求分发到不同的 Tomcat 来缓解服务器的压力&#xff0c;但是这里存在一个问题&#xff1a; 当同一个用户第一次访问tomcat_8111 并且登录成功&#xff0c; 而第二次访问却被分配到了tomcat_8222&#xff0c; 这里…

Spring Session + Redis实现Session共享

功能需求 通常情况下&#xff0c;Tomcat的Servlet容器会默认将Session保存在内存中。如果是单个服务器实例的应用&#xff0c;将Session保存在服务器内存中是一个常用的选择&#xff0c;但是随着服务器数量的增多&#xff0c;这种方法变得不容易扩展。 比如上图中&#xff0c…

nginx实现session共享

在nginx实现了负载均衡之后&#xff0c;用户第一次请求的tomcat和第二次请求的tomcat基本不是同一个&#xff0c;但是你在第一次请求放在session中的值只有一个tomcat1才有&#xff0c;第二个请求的那个tomcat2里面是没有的。这样就出现了用户不停登入的情况。为了解决这个sess…

tomcat配置session共享

tomcat官网 打开 tomcat 官网&#xff1a; http://tomcat.apache.org/ &#xff0c; 找到需要配置的tomcat版本的文档&#xff0c;这里以 tomcat7 为例&#xff0c; 找到对应的 Clustering 配置&#xff08;因为配置session共享&#xff0c;就是配置集群&#xff09;&#xff…

Nginx——Session共享

文章目录 其他文章1、Session共享1.1、session 一致性解决方案1.1.1、session 复制1.1.2、共享 session 1.2、安装memcached1.2.1、 node2 和 node3 上安装 jdk 和 tomcat1.2.2、nginx1 上安装 memcached 其他文章 Nginx——安装Nginx1.6.1 Nginx——工作模型 Nginx——配置文…

Springboot整合springsession实现session共享

Springboot整合springsession实现session共享 简介 session我们之前有介绍过&#xff08;可见springboot整合springsecurity&#xff09;&#xff0c;简单来说就是将用户信息或者数据存储在服务器上&#xff0c;通常用于验证用户身份或者避免通过获取相关信息。 但是&#x…

集群产生的session共享问题

前言&#xff1a; 为了解决集群产生的session共享问题&#xff0c;我们首先需要知道以下三个概念&#xff1a; cookiesessionnginx ​ 下面将逐个分析。 Cookie 1.Cookie是什么 ​cookie的中文翻译是曲奇&#xff0c;小甜饼的意思。cookie其实就是一些数据信息&#xff0c…

Websocket Session 共享解决

Websocket Session 共享解决&#xff08;方案一&#xff09; 既然Websocket Session 不能直接序列化然后存储&#xff0c;而且如果用户请求的时候&#xff0c;开始与集群中的A 节点创建链接&#xff0c;就算把这个 Session 拿到B 节点去再给用户Push 消息&#xff0c;应该也…

使用redis实现session共享

继上一遍文章&#xff0c;使用nginx实现负载均衡以后&#xff0c;新的问题又产生了&#xff0c;当使用负载均衡以后&#xff0c;我们如何实现session共享&#xff0c;因为session是保存在服务端的&#xff0c;对于单体应用来说这不是问题&#xff0c;因为只有一个服务器&#x…

Session共享问题-session原理

问题1&#xff1a;微服务情况下。域名不同。session不共享 问题2&#xff1a;分布式系统下。同一个域名下&#xff0c;多个服务。session会出现不同步问题&#xff08;例如第一次访问的是A服务&#xff0c;保存了。第二次访问了B,查询到的还是没信息&#xff09; 解决&#xf…

Springboot:Session共享

1、概述 传统单机web应用中&#xff0c;一般使用tomcat/jetty等web容器时&#xff0c;用户的session都是由容器管理。浏览器使用cookie中记录sessionId&#xff0c;容器根据sessionId判断用户是否存在会话session。这里的限制是&#xff0c;session存储在web容器中&#xff0c…

Java -- SpringSession实现session共享

在集群系统中&#xff0c;经常需要将 Session 进行共享。不然会出问题&#xff1a;用户在系统A上登陆以后&#xff0c;假如后续的一些操作被负载均衡到系统B上面&#xff0c;系统B发现本机上没有这个用户的 Session &#xff0c;会强制让用户重新登陆。 如在同域名&#xff0c…

shiro分布式session共享

当我们开发的程序需要跑在多个tomcat容器或者多台机器上时&#xff0c;shiro的默认session存储就不能满足我们的需求了&#xff0c;其中shiro默认的session是存储在运行jvm内存中的&#xff0c;使用的AbstractSessionDAO抽象类的一个子类MemorySessionDAO&#xff0c;当我们需要…

tomcat+redis实现session共享

注意&#xff1a;在实际应用中&#xff0c;发现该方案会不定期导致Tomcat假死&#xff08;怀疑Redis连接未释放&#xff09;&#xff0c;慎用。 服务器192.168.14.132和192.168.14.133&#xff0c; 均已经安装tomcat&#xff0c;tomcat安装过程不再赘述。 采用192.168.14.132…

Session共享问题

Session共享及Session保持或者叫做Session⼀致性 1、Session问题原因分析 出现这个问题的原因&#xff0c;从根本上来说是因为Http协议是无状态的协议。客户端和服务端在某次会话中产生的数据不会被保留下来&#xff0c;所以第⼆次请求服务端无法认识到你曾经来过&#xff0c;…

session共享(redis实现)

引言 大厂很多项目都是部署到多台服务器上&#xff0c;这些服务器在各个地区都存在&#xff0c;当我们访问服务时虽然执行的是同一个服务&#xff0c;但是可能是不同服务器运行的&#xff1b; 在我学习项目时遇到这样一个登录情景&#xff0c;假设有如下三台服务器&#xff0…

如何实现session共享的几种解决方案?

先了解一下为什么会出现这种session共享的解决方案&#xff1f; 随着互联网公司的项目在微服务和分布式的环境下进行的搭建&#xff0c;导致一个项目可能分别部署在几个甚至很多的服务器集群下&#xff0c;此时就会出现一个问题当用户进行一个session会话的时候&#xff0c;比…

session共享几种方式

Session共享三种方式 1.nginx基于ip_hash负载均衡。 只需要更改nginx.conf配置文件。添加ip_hash就可以了。 缺点&#xff1a;1.由于ip_hash分配tomcat的时候用的是除法&#xff0c;所以新添加一台服务器会导致分配不到原来程序上&#xff0c;session会丢失。2.同一个公网ip…

session共享学习

一、Session共享 1 什么是Session共享    是指在一个浏览器对应多个Web服务时&#xff0c;服务端的Session数据需要共享。 2 Session共享应用场景 单点登录Web服务器集群等 3 Session共享常见的解决方案 3.1 Session复制       通过对应用服务器的配置开启服务器的Sess…

redis实现session共享

session共享 什么是session&#xff1f; 由于 HTTP 协议是无状态的协议&#xff0c;所以服务端需要记录用户的状态时&#xff0c;就需要用某种机制来识具体的用户。Session 是另一种记录客户状态的机制&#xff0c;不同的是 Cookie 保存在客户端浏览器中&#xff0c;而 Sessi…