SpringCloud开启session共享并存储到Redis的实现

文章详细介绍了如何在SpringCloudGateway中配置session共享到Redis,包括添加相关依赖、配置Redis连接、创建配置类、修改过滤器以处理session,以及针对同域和跨域部署的不同策略。通过这些步骤,解决了因Gateway导致的session丢失问题。
摘要由CSDN通过智能技术生成

原文:SpringCloud开启session共享并存储到Redis的实现 / 张生荣

目录

  • 一、原架构
  • 二、调整架构以及相应的代码
    • 1、Redis和session的配置
    • 2、增加配置类
    • 3、应答过滤器增加session设置
    • 4、增加控制台处理的过滤器ConsoleFilter
    • 5、前端请求中增加(跨域时)
  • 三、部署模式
    • 1、同域
    • 2、跨域
  • 总结

备注:以下所有的gateway均指SpringCloud Gateway

一、原架构

前端<->gateway<->console后端

原来session是在console-access中维护的,当中间有了一层gateway之后,gateway会认为session变了,从而将session的cookie信息重置,导致无法在前端的后续请求无法将cookie带上来

如下图所示的spring-web的代码中这个state会变成State.NEW而非State.STARTED

在这种情况下,部署的时候只有跳过gateway才能正常进行

即按照如下方式才能进行session的判断

前端<->console后端

二、调整架构以及相应的代码

整个业务处理和原来的没有任何改变

将session的判断控制挪到gateway当中

首先将console后端中登录以及后续业务当中涉及到session处理的部分都删除

然后开始改造gateway

1、Redis和session的配置

gateway的pom.xml增加

 
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.session</groupId>
  7. <artifactId>spring-session-data-redis</artifactId>
  8. </dependency>

因为需要依赖Redis了,所以启动类当中删除RedisAutoConfiguration.class

Nacos或者配置文件增加redis配置

 
  1. spring.redis.host=127.0.0.1
  2. spring.redis.port=6379
  3. spring.redis.username=redis7
  4. spring.redis.password=XXX
  5. spring.redis.database=10
  6. spring.redis.pool.max-active=100
  7. spring.redis.pool.max-wait=500
  8. spring.redis.pool.max-idle=10
  9. spring.redis.pool.min-idle=10
  10. spring.redis.timeout=1000

2、增加配置类

 
  1. /**
  2. * 指定saveMode为ALWAYS 功能和flushMode类似
  3. *
  4. * @author fengwei
  5. * @since 2022/11/8
  6. */
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.http.HttpCookie;
  11. import org.springframework.session.SaveMode;
  12. import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
  13. import org.springframework.util.MultiValueMap;
  14. import org.springframework.web.server.ServerWebExchange;
  15. import org.springframework.web.server.session.CookieWebSessionIdResolver;
  16. import org.springframework.web.server.session.WebSessionIdResolver;
  17. import java.util.Base64;
  18. import java.util.Collections;
  19. import java.util.List;
  20. import java.util.stream.Collectors;
  21. @EnableRedisWebSession(saveMode = SaveMode.ALWAYS, maxInactiveIntervalInSeconds = 1200)
  22. @Configuration
  23. @Slf4j
  24. public class RedisSessionConfig {
  25. /**
  26. * @return
  27. * @reference https://docs.spring.io/spring-session/reference/guides/boot-webflux-custom-cookie.html
  28. */
  29. @Bean
  30. public WebSessionIdResolver webSessionIdResolver() {
  31. CustomWebSessionIdResolver customWebSessionIdResolver = new CustomWebSessionIdResolver();
  32. //以下四项配置主要用于跨域调用让客户端处理cookie信息;若同域调用,下面四行可删除
  33. customWebSessionIdResolver.addCookieInitializer((builder) -> builder.httpOnly(true));
  34. customWebSessionIdResolver.addCookieInitializer((builder) -> builder.path("/"));
  35. customWebSessionIdResolver.addCookieInitializer((builder) -> builder.sameSite("None"));
  36. customWebSessionIdResolver.addCookieInitializer((builder) -> builder.secure(true));
  37. return customWebSessionIdResolver;
  38. }
  39. private static class CustomWebSessionIdResolver extends CookieWebSessionIdResolver {
  40. // 重写resolve方法 对SESSION进行base64解码
  41. @Override
  42. public List<String> resolveSessionIds(ServerWebExchange exchange) {
  43. MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies();
  44. // 获取SESSION
  45. List<HttpCookie> cookies = cookieMap.get(getCookieName());
  46. if (cookies == null) {
  47. return Collections.emptyList();
  48. }
  49. return cookies.stream().map(HttpCookie::getValue).map(this::base64Decode).collect(Collectors.toList());
  50. }
  51. private String base64Decode(String base64Value) {
  52. try {
  53. byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
  54. String decodedCookieString = new String(decodedCookieBytes);
  55. log.debug("base64Value:{}, decodedCookieString:{} ", base64Value, decodedCookieString);
  56. return decodedCookieString;
  57. } catch (Exception ex) {
  58. //如果转不了base64,就认为原始值就是返回的
  59. log.debug("Unable to Base64 decode value:{} ", base64Value);
  60. return base64Value;
  61. }
  62. }
  63. }
  64. }

3、应答过滤器增加session设置

在ResponseLogFilter类中增加

 
  1. /*如果是控制台登录,则从里面取出securityRandom存在websession里面*/
  2. if (request.getPath().toString().startsWith("/console/access/user/login")) {
  3. JSONObject jsonObject = JSONObject.parseObject(finalResponseBody);
  4. if ("0000".equals(jsonObject.getString("result"))) {
  5. JSONObject jsonObjectData = (JSONObject) jsonObject.get("data");
  6. String securityRandom = (String) jsonObjectData.get("securityrandom");
  7. exchange.getSession().subscribe(webSession -> {
  8. webSession.getAttributes().put("securityrandom", securityRandom);
  9. });
  10. try {
  11. //给200毫秒让进行session设置
  12. Thread.sleep(200);
  13. } catch (InterruptedException e) {
  14. throw new RuntimeException(e);
  15. }
  16. }
  17. }

4、增加控制台处理的过滤器ConsoleFilter

首选在配置文件或者Nacos增加配置项

 
  1. #Y标识需要检查session;N表示不检查session。开发的时候可以配置为N
  2. websession.ifcheck=Y

过滤器ConsoleFilter

 
  1. import com.alibaba.cloud.commons.lang.StringUtils;
  2. import com.jieyi.util.OrderedConstant;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  6. import org.springframework.cloud.gateway.filter.GlobalFilter;
  7. import org.springframework.core.Ordered;
  8. import org.springframework.core.io.buffer.DataBuffer;
  9. import org.springframework.http.HttpStatus;
  10. import org.springframework.http.server.reactive.ServerHttpRequest;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.server.ServerWebExchange;
  13. import reactor.core.publisher.Flux;
  14. import reactor.core.publisher.Mono;
  15. import java.nio.charset.StandardCharsets;
  16. import java.util.Arrays;
  17. import java.util.List;
  18. /**
  19. * 控制台登录的过滤器,主要是拿用户凭证的securityRandom
  20. *
  21. * @author fengwei
  22. * @date 2022-11-8
  23. */
  24. @Component
  25. @Slf4j
  26. public class ConsoleFilter implements GlobalFilter, Ordered {
  27. private static final List<String> WHITE_LIST = Arrays.asList("/console/access/user/getOtp/V1", "/console/access/user/login/V1");
  28. @Value("${websession.ifcheck}")
  29. private String websessionIfcheck;
  30. @Override
  31. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  32. ServerHttpRequest request = exchange.getRequest();
  33. String path = request.getPath().toString();
  34. //不校验session就直接通过了
  35. if (!"Y".equals(websessionIfcheck)) {
  36. return chain.filter(exchange);
  37. }
  38. //在url白名单中放行
  39. boolean isWhiteUrl = WHITE_LIST.stream().anyMatch(path::endsWith);
  40. if (isWhiteUrl) {
  41. return chain.filter(exchange);
  42. }
  43. if (path.startsWith("/console")) {
  44. return exchange.getSession().flatMap(webSession -> {
  45. String securityrandomInSession = webSession.getAttribute("securityrandom");
  46. log.info("securityrandomInSession:{}", securityrandomInSession);
  47. if (StringUtils.isEmpty(securityrandomInSession)) {
  48. byte[] bytes = "{\"status\":\"401\",\"msg\":\"Not login or login timeout\"}".getBytes(StandardCharsets.UTF_8);
  49. DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
  50. exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
  51. return exchange.getResponse().writeWith(Flux.just(buffer));
  52. }
  53. return chain.filter(exchange);
  54. });
  55. } else {
  56. return chain.filter(exchange);
  57. }
  58. }
  59. @Override
  60. public int getOrder() {
  61. return OrderedConstant.LOGGING_FILTER;
  62. }
  63. }

5、前端请求中增加(跨域时)

 
  1. withCredentials = true

只有增加这个请求才能携带和处理cookie

三、部署模式

1、同域

对于同域的部署http和https均可(当然更建议https)

提供一个nginx同域部署的参考:

 
  1. server {
  2. #console-samedomain-test
  3. listen 38093 ssl;
  4. proxy_set_header Host $host:38093;
  5. root html;
  6. index index.html index.htm;
  7. ssl_certificate cert/server.crt;
  8. ssl_certificate_key cert/server.key;
  9. ssl_session_timeout 5m;
  10. ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
  11. ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  12. ssl_prefer_server_ciphers on;
  13. location / {
  14. proxy_pass http://127.0.0.1:30000/;
  15. }
  16. location /ccuconsole {
  17. proxy_pass http://127.0.0.1:5120/ccuconsole;
  18. }
  19. }

该配置为https,走的38093端口

前端页面访问https://ip:38093/ccuconsole

所有的请求都通过该同域的ip和端口转发到http://127.0.0.1:30000对应的服务(确保该服务中不存在/ccuconsole开头的路径)中

2、跨域

对于跨域的部必须使用https(现在的浏览器版本的要求,浏览器已不再支持http的跨域了)

提供一个nginx跨域部署的参考:

 
  1. server {
  2. #console-web-crossdomain-test
  3. listen 38091 ssl;
  4. proxy_set_header Host $host:38091;
  5. root html;
  6. index index.html index.htm;
  7. ssl_certificate cert/server.crt;
  8. ssl_certificate_key cert/server.key;
  9. ssl_session_timeout 5m;
  10. ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
  11. ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  12. ssl_prefer_server_ciphers on;
  13. location /ccuconsole {
  14. proxy_pass http://127.0.0.1:5120/ccuconsole;
  15. }
  16. }
  17. server {
  18. #console-crossdomain-test
  19. listen 38092 ssl;
  20. proxy_set_header Host $host:38092;
  21. root html;
  22. index index.html index.htm;
  23. ssl_certificate cert/server.crt;
  24. ssl_certificate_key cert/server.key;
  25. ssl_session_timeout 5m;
  26. ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
  27. ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  28. ssl_prefer_server_ciphers on;
  29. location / {
  30. proxy_pass http://127.0.0.1:30000/;
  31. }
  32. }

前端页面访问https://ip:38091/ccuconsole

所有的请求都通过该同域的ip和38092转发到http://127.0.0.1:30000对应的服务(确保该服务中有无/ccuconsole开头的路径并不影响,但是为了可切换同域部署,不推荐存在/ccuconsole开头的路径)中

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值