我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

备注:以下所有的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增加

<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了,所以启动类当中删除RedisAutoConfiguration.class

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

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.username=redis7
spring.redis.password=XXX
spring.redis.database=10
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=500
spring.redis.pool.max-idle=10
spring.redis.pool.min-idle=10
spring.redis.timeout=1000

2、增加配置类



import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpCookie;
import org.springframework.session.SaveMode;
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.session.CookieWebSessionIdResolver;
import org.springframework.web.server.session.WebSessionIdResolver;

import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@EnableRedisWebSession(saveMode = SaveMode.ALWAYS, maxInactiveIntervalInSeconds = 1200)
@Configuration
@Slf4j
public class RedisSessionConfig {
    
    @Bean
    public WebSessionIdResolver webSessionIdResolver() {
        CustomWebSessionIdResolver customWebSessionIdResolver = new CustomWebSessionIdResolver();
        //以下四项配置主要用于跨域调用让客户端处理cookie信息;若同域调用,下面四行可删除
        customWebSessionIdResolver.addCookieInitializer((builder) -> builder.httpOnly(true));
        customWebSessionIdResolver.addCookieInitializer((builder) -> builder.path("/"));
        customWebSessionIdResolver.addCookieInitializer((builder) -> builder.sameSite("None"));
        customWebSessionIdResolver.addCookieInitializer((builder) -> builder.secure(true));
        return customWebSessionIdResolver;
    }

    private static class CustomWebSessionIdResolver extends CookieWebSessionIdResolver {
        // 重写resolve方法 对SESSION进行base64解码
        @Override
        public List<String> resolveSessionIds(ServerWebExchange exchange) {
            MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies();
            // 获取SESSION
            List<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);
                String decodedCookieString = new String(decodedCookieBytes);
                log.debug("base64Value:{}, decodedCookieString:{} ", base64Value, decodedCookieString);
                return decodedCookieString;
            } catch (Exception ex) {
                //如果转不了base64,就认为原始值就是返回的
                log.debug("Unable to Base64 decode value:{} ", base64Value);
                return base64Value;
            }
        }
    }

}

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

在ResponseLogFilter类中增加


if (request.getPath().toString().startsWith("/console/access/user/login")) {
    JSONObject jsonObject = JSONObject.parseObject(finalResponseBody);
    if ("0000".equals(jsonObject.getString("result"))) {
        JSONObject jsonObjectData = (JSONObject) jsonObject.get("data");
        String securityRandom = (String) jsonObjectData.get("securityrandom");
        exchange.getSession().subscribe(webSession -> {
            webSession.getAttributes().put("securityrandom", securityRandom);
        });
        try {
            //给200毫秒让进行session设置
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

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

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

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

过滤器ConsoleFilter

import com.alibaba.cloud.commons.lang.StringUtils;
import com.jieyi.util.OrderedConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;


@Component
@Slf4j
public class ConsoleFilter implements GlobalFilter, Ordered {
    private static final List<String> WHITE_LIST = Arrays.asList("/console/access/user/getOtp/V1", "/console/access/user/login/V1");

    @Value("${websession.ifcheck}")
    private String websessionIfcheck;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().toString();


        //不校验session就直接通过了
        if (!"Y".equals(websessionIfcheck)) {
            return chain.filter(exchange);
        }
        //在url白名单中放行
        boolean isWhiteUrl = WHITE_LIST.stream().anyMatch(path::endsWith);
        if (isWhiteUrl) {
            return chain.filter(exchange);
        }

        if (path.startsWith("/console")) {
            return exchange.getSession().flatMap(webSession -> {
                String securityrandomInSession = webSession.getAttribute("securityrandom");
                log.info("securityrandomInSession:{}", securityrandomInSession);
                if (StringUtils.isEmpty(securityrandomInSession)) {
                    byte[] bytes = "{\"status\":\"401\",\"msg\":\"Not login or login timeout\"}".getBytes(StandardCharsets.UTF_8);
                    DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    return exchange.getResponse().writeWith(Flux.just(buffer));
                }
                return chain.filter(exchange);
            });

        } else {
            return chain.filter(exchange);
        }

    }

    @Override
    public int getOrder() {
        return OrderedConstant.LOGGING_FILTER;
    }
}

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

withCredentials = true

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

三、部署模式

1、同域

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

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

server {
  #console-samedomain-test
  listen 38093 ssl;
  proxy_set_header Host $host:38093;
  root html;
  index index.html index.htm;
  ssl_certificate      cert/server.crt;
  ssl_certificate_key  cert/server.key;
  ssl_session_timeout  5m;
  ssl_ciphers  ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers  on;
  location / {
    proxy_pass http://127.0.0.1:30000/;
  }
  location /ccuconsole {
    proxy_pass http://127.0.0.1:5120/ccuconsole;
  }
}

该配置为https,走的38093端口

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

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

2、跨域

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

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

server {
  #console-web-crossdomain-test
  listen 38091 ssl;
  proxy_set_header Host $host:38091;
  root html;
  index index.html index.htm;
  ssl_certificate      cert/server.crt;
  ssl_certificate_key  cert/server.key;
  ssl_session_timeout  5m;
  ssl_ciphers  ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers  on;
  location /ccuconsole {
    proxy_pass http://127.0.0.1:5120/ccuconsole;
  }
}

server {
  #console-crossdomain-test
  listen 38092 ssl;
  proxy_set_header Host $host:38092;
  root html;
  index index.html index.htm;
  ssl_certificate      cert/server.crt;
  ssl_certificate_key  cert/server.key;
  ssl_session_timeout  5m;
  ssl_ciphers  ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers  on;
  location / {
    proxy_pass http://127.0.0.1:30000/;
  }
}

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

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

总结

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

这篇文章主要介绍了SpringCloud开启session共享并存储到Redis的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-02-14

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录