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

Spring Cloud Gateway中session共享相关推荐

  1. spring cloud gateway中解决第一次请求失败的问题

    spring cloud gateway中解决第一次请求失败的问题 参考文章: (1)spring cloud gateway中解决第一次请求失败的问题 (2)https://www.cnblogs. ...

  2. Spring Cloud Gateway中异常处理

    Spring Cloud Gateway中异常处理 参考文章: (1)Spring Cloud Gateway中异常处理 (2)https://www.cnblogs.com/viaiu/p/1040 ...

  3. Spring Cloud Gateway 3.1.3最新版中文手册官网2022

    Spring Cloud Gateway 3.1.3 该项目提供了一个库,用于在 Spring WebFlux 之上构建 API 网关.Spring Cloud Gateway 旨在提供一种简单而有效 ...

  4. 微服务网关spring cloud gateway入门详解

    1.API网关 API 网关是一个处于应用程序或服务( REST API 接口服务)之前的系统,用来管理授权.访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的 ...

  5. 实战 Spring Cloud Gateway 之限流篇

    来源:https://www.aneasystone.com/archives/2020/08/spring-cloud-gateway-current-limiting.html 话说在 Sprin ...

  6. 网关 翻译版本 spring cloud gateway

    Spring Cloud Gateway 官网原文地址 https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html ...

  7. 这可能是全网Spring Cloud Gateway限流最完整的方案了!

        作者:aneasystone     https://www.aneasystone.com/ 话说在 Spring Cloud Gateway 问世之前,Spring Cloud 的微服务世 ...

  8. spring cloud gateway之filter篇

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 在上一篇文章详细的介绍了Gateway的Predict,Predict决定了请求由哪一个路由处理, ...

  9. Spring Cloud Gateway重试机制

    前言 重试,我相信大家并不陌生.在我们调用Http接口的时候,总会因为某种原因调用失败,这个时候我们可以通过重试的方式,来重新请求接口. 生活中这样的事例很多,比如打电话,对方正在通话中啊,信号不好啊 ...

最新文章

  1. Nginx+Tomcat动静分离及Nginx优化(企业案例)
  2. 开放下载!《阿里云实时数仓Hologres最佳实践合集》
  3. pytorch机器学习_机器学习— PyTorch
  4. python定义函数prime判断是否是素数_用自定义函数判断素数 用C语言编写自定义函数prime(int x),判断x是否为素数?...
  5. 文章页网址有.html,Phpcms V9文章内容页自定义HTML网址技巧
  6. python数学建模可视化,[Python与数学建模-数据处理与可视化]-3数据处理工具Pandas...
  7. 深入学习Java虚拟机(三)
  8. 深入Atlas系列:客户端网络访问基础结构(上) - WebRequest的工作流程与生命周期...
  9. paip.快速查找定位文件VS2010
  10. 测试用例(功能用例)——人员管理、资产入库
  11. 《Java设计模式》刘伟 超清晰版本 下载链接
  12. 有关微博营销的社交营销打法
  13. Ubuntu交叉编译工具链安装
  14. 高旭东:科普返利网站模式,做到知己知彼放心购物!
  15. 基频和倍频的概念_基频峰,泛频峰,倍频峰,二倍频峰的区别
  16. DBpedia Introduction
  17. redis未授权访问
  18. HTTP状态码---服务器错误
  19. win7 docker的受难记——exit status 255的终极解决
  20. Leetcode P44 java一次遍历

热门文章

  1. 机械硬盘每十秒左右“咔哒”响一声是怎么回事?
  2. 结婚后谁管钱更高效?怎样越管越多?
  3. css实现背景模糊效果
  4. python中返回上一步操作的代码_python基础-文件操作
  5. C++实现有限状态机
  6. 手机爱奇艺/腾讯视频桌面像推送到电视一样推送到电脑
  7. 排查思路-Dell R730xd PCIe 指示灯和运行状况指示灯闪烁显琥珀色
  8. int linux 原子操作_Linux原子操作
  9. 聊一聊北美算法工程师日常
  10. 单链表之o(1)删除指定节点(Java)