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增加

<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、增加配置类

/**
 * 指定saveMode为ALWAYS 功能和flushMode类似
 *
 * @author fengwei
 * @since 2022/11/8
 */

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 {
    /**
     * @return
     * @reference https://docs.spring.io/spring-session/reference/guides/boot-webflux-custom-cookie.html
     */
    @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类中增加

/*如果是控制台登录,则从里面取出securityRandom存在websession里面*/
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;

/**
 * 控制台登录的过滤器,主要是拿用户凭证的securityRandom
 *
 * @author fengwei
 * @date 2022-11-8
 */
@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开头的路径)中

总结

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

(0)

相关推荐

  • SpringCloud修改Feign日志记录级别过程浅析

    目录 前言 1. 介绍 2. 方式一 3. 方式二 前言 本次示例代码的文件结构如下图所示. 1. 介绍 Feign 允许我们自定义配置,下面是 Feign 可以修改的配置. 类型 作用 说明 feign.Logger.Level 修改日志级别 包含四种不同级别:NONE.BASIC.HEADERS.FULL feign.codec.Decoder 响应结果的解析器 HTTP 远程调用的结果做解析,例如解析 JSON 字符串反序列化成 Java 对象 feign.codec.Encoder 请求

  • SpringCloud @RefreshScope刷新机制浅析

    目录 一.前言 二.@Scope 三.RefreshScope 的实现原理 四.总结 一.前言 用过Spring Cloud的同学都知道在使用动态配置刷新的我们要配置一个@RefreshScope 在类上才可以实现对象属性的的动态更新,本着知其所以然的态度,晚上没事儿又把这个点回顾了一下,下面就来简单的说下自己的理解. 总览下,实现@RefreshScope 动态刷新的就需要以下几个: @ Scope @RefreshScope RefreshScope GenericScope Scope C

  • Spring Cloud Gateway替代zuul作为API网关的方法

    目录 第一,pom文件 第二,项目结构 第三,项目代码和运行截图 运行效果图 参考文档: 本文简要介绍如何使用Spring Cloud Gateway 作为API 网关(不是使用zuul作为网关),关于Spring Cloud Gateway和zuul的性能比较本文不再赘述,基本可以肯定Spring Cloud Finchley版本的gateway比zuul 1.x系列的性能和功能整体要好. 特别提醒:Spring Cloud Finchley版本中,即使你引入了spring-cloud-sta

  • Spring Cloud Alibaba 整合Nacos的详细使用教程

    目录 一.前言 二.常用服务注册中心介绍 2.1 dubbo服务注册示意图 2.2 常用注册中心对比 三.nacos介绍 3.1  什么是nacos 3.2 nacos 特点 3.3 nacos生态链地图 四.nacos部署 4.1 下载安装包 4.2 修改脚本启动模式 4.3  启动nacos 服务 五.Spring Cloud Alibaba 整合Nacos 5.1  Spring Cloud Alibaba版本选型 5.2  实验整合案例说明 5.3  整合完整过程 5.3.1 创建聚合工

  • SpringCloud Gateway动态路由配置详解

    目录 路由 动态 路由模型实体类 动态路径配置 路由模型JSON数据 路由 gateway最主要的作用是,提供统一的入口,路由,鉴权,限流,熔断:这里的路由就是请求的转发,根据设定好的某些条件,比如断言,进行转发. 动态 动态的目的是让程序更加可以在运行的过程中兼容更多的业务场景. 涉及到两个服务,一个是门户服务(作用是提供给运营人员管理入口--包括:管理路由.绑定路由),一个是网关服务(gateway组件,为门户服务提供:查询路由信息.添加路由.删除路由.编辑路由接口). 路由模型实体类 /*

  • SpringCloud使用Feign实现远程调用流程详细介绍

    目录 前言 1. 导入依赖坐标 2. 开启Feign自动装配 3. 声明远程调用 4. 替代RestTemplate 5. 测试 前言 本次示例代码的文件结构如下图所示. 1. 导入依赖坐标 在 order-service 的 pom.xml 文件中导入 Feign 的依赖坐标. <!-- Feign远程调用客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifa

  • springcloud-gateway集成knife4j的示例详解

    目录 springcloud-gateway集成knife4j 环境信息 环境信息 准备工作 网关集成knife4j 编写配置类Knife4jGatewayConfig 测试验证 相关资料 springcloud-gateway集成knife4j 环境信息 环境信息 spring-boot:2.6.3 spring-cloud-alibaba:2021.0.1.0 knife4j-openapi2-spring-boot-starter:4.0.0 准备工作 各微服务&网关引入依赖 <dep

  • 一文吃透Spring Cloud gateway自定义错误处理Handler

    目录 正文 AbstractErrorWebExceptionHandler isDisconnectedClientError方法 isDisconnectedClientErrorMessage方法: 小结 NestedExceptionUtils getRoutingFunction logError write 其他的方法 afterPropertiesSet renderDefaultErrorView renderErrorView DefaultErrorWebExceptionH

  • SpringCloud启动失败问题汇总

    目录 SpringCloud启动失败问题 Nacos配置读取失败 解决方案 总结 SpringCloud启动失败问题 Nacos配置读取失败 org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1        at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:218) ~

  • SpringCloud @RefreshScope刷新机制深入探究

    目录 梳理过程如下 @RefreshScope ScopedProxyMode RefreshAutoConfiguration NacosConfigService ClientWorker CacheData AbstractSharedListener NacosContextRefresher RefreshEventListener ContextRefresher EventPublishingRunListener RestartListener Java连接nacos后会定时心跳

  • Spring Cloud Gateway远程命令执行漏洞分析(CVE-2022-22947)

    目录 漏洞描述 环境搭建 漏洞复现 声明:本文仅供学习参考,其中涉及的一切资源均来源于网络,请勿用于任何非法行为,否则您将自行承担相应后果,本人不承担任何法律及连带责任. 漏洞描述 使用Spring Cloud Gateway的应用程序在Actuator端点启用.公开和不安全的情况下容易受到代码注入的攻击.攻击者可以恶意创建允许在远程主机上执行任意远程执行的请求. 当攻击者可以访问actuator API时,就可以利用该漏洞执行任意命令. 影响范围 Spring Cloud Gateway <

  • Spring Cloud Ribbon 负载均衡使用策略示例详解

    目录 一.前言 二.什么是 Ribbon 2.1 ribbon简介 2.1.1  ribbon在负载均衡中的角色 2.2 客户端负载均衡 2.3 服务端负载均衡 2.4 常用负载均衡算法 2.4.1 随机算法 2.4.2 轮询算法 2.4.3 加权轮询算法 2.4.4 IP地址hash 2.4.5 最小链接数 三.Ribbon中负载均衡策略总探究 3.1 nacos中使用ribbon过程 3.1.1 添加配置类 3.1.2 接口层调用 3.2 Ribbon中负载均衡配置策略 3.2.1 IRul

  • springcloud整合openfeign使用实例详解

    目录 一.前言 二.微服务接口之间的调用问题 2.1 Httpclient 2.2 Okhttp 2.3 HttpURLConnection 2.4 RestTemplate 三.openfeign介绍 3.1 什么是 openfeign 3.2  openfeign优势 四.Spring Cloud Alibaba整合OpenFeign 4.1 前置准备 4.2  完整整合步骤 4.2.1 order模块添加feign依赖 4.2.2  添加feign接口类 4.2.3  调整调用的方法 4.

  • SpringCloud Hystrix熔断器使用方法介绍

    目录 Hystrix(hi si ju ke si)概述 Hystix 主要功能 隔离 Hystrix 降级 Hystrix降级-服务提供方 初始化程序和Fiegn程序一致 Hystrix降级-服务消费方 provider与Fiegin一致 Hystrix 熔断 Hystrix 熔断监控 Turbine聚合监控 搭建监控模块 修改被监控模块 启动测试 Hystrix(hi si ju ke si)概述 Hystix 是 Netflix 开源的一个延迟和容错库,用于隔离访问远程服务.第三方库,防止

  • SpringCloud Gateway路由组件详解

    目录 简介 核心概念 具体示例 GlobalFilter 简介   Gateway是SpringCloud Alibaba中的路由组件(前身是Zuul),作为浏览器端请求的统一入口.当项目采用微服务模式时,若包含了路由模块,浏览器端的请求都不会直接请求含有业务逻辑的各个业务模块,而是请求这个路由模块,然后再由它来转发到各个业务模块去. 核心概念   Gateway中的三个核心概念:路由.断言(Predicate).过滤器.   路由:由唯一id.目的url.断言和过滤组成   断言:即路由规则,

随机推荐