SpringCloud開啟session共享并存儲(chǔ)到Redis的實(shí)現(xiàn)
備注:以下所有的gateway均指SpringCloud Gateway
一、原架構(gòu)
前端<->gateway<->console后端
原來session是在console-access中維護(hù)的,當(dāng)中間有了一層gateway之后,gateway會(huì)認(rèn)為session變了,從而將session的cookie信息重置,導(dǎo)致無法在前端的后續(xù)請(qǐng)求無法將cookie帶上來
如下圖所示的spring-web的代碼中這個(gè)state會(huì)變成State.NEW而非State.STARTED
在這種情況下,部署的時(shí)候只有跳過gateway才能正常進(jìn)行
即按照如下方式才能進(jìn)行session的判斷
前端<->console后端
二、調(diào)整架構(gòu)以及相應(yīng)的代碼
整個(gè)業(yè)務(wù)處理和原來的沒有任何改變
將session的判斷控制挪到gateway當(dāng)中
首先將console后端中登錄以及后續(xù)業(yè)務(wù)當(dāng)中涉及到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>
因?yàn)樾枰蕾嘡edis了,所以啟動(dòng)類當(dāng)中刪除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(); //以下四項(xiàng)配置主要用于跨域調(diào)用讓客戶端處理cookie信息;若同域調(diào)用,下面四行可刪除 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方法 對(duì)SESSION進(jìn)行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) { //如果轉(zhuǎn)不了base64,就認(rèn)為原始值就是返回的 log.debug("Unable to Base64 decode value:{} ", base64Value); return base64Value; } } } }
3、應(yīng)答過濾器增加session設(shè)置
在ResponseLogFilter類中增加
/*如果是控制臺(tái)登錄,則從里面取出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毫秒讓進(jìn)行session設(shè)置 Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
4、增加控制臺(tái)處理的過濾器ConsoleFilter
首選在配置文件或者Nacos增加配置項(xiàng)
#Y標(biāo)識(shí)需要檢查session;N表示不檢查session。開發(fā)的時(shí)候可以配置為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; /** * 控制臺(tái)登錄的過濾器,主要是拿用戶憑證的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(); //不校驗(yàn)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、前端請(qǐng)求中增加(跨域時(shí))
withCredentials = true
只有增加這個(gè)請(qǐng)求才能攜帶和處理cookie
三、部署模式
1、同域
對(duì)于同域的部署http和https均可(當(dāng)然更建議https)
提供一個(gè)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
所有的請(qǐng)求都通過該同域的ip和端口轉(zhuǎn)發(fā)到http://127.0.0.1:30000對(duì)應(yīng)的服務(wù)(確保該服務(wù)中不存在/ccuconsole開頭的路徑)中
2、跨域
對(duì)于跨域的部必須使用https(現(xiàn)在的瀏覽器版本的要求,瀏覽器已不再支持http的跨域了)
提供一個(gè)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
所有的請(qǐng)求都通過該同域的ip和38092轉(zhuǎn)發(fā)到http://127.0.0.1:30000對(duì)應(yīng)的服務(wù)(確保該服務(wù)中有無/ccuconsole開頭的路徑并不影響,但是為了可切換同域部署,不推薦存在/ccuconsole開頭的路徑)中
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringCloud Hystrix熔斷器使用方法介紹
- SpringCloud Gateway動(dòng)態(tài)路由配置詳解
- Spring?Cloud?Gateway遠(yuǎn)程命令執(zhí)行漏洞分析(CVE-2022-22947)
- SpringSecurit鹽值加密的密碼驗(yàn)證以及強(qiáng)密碼驗(yàn)證過程
- Spring?Cloud?Alibaba實(shí)現(xiàn)服務(wù)的無損下線功能(案例講解)
- springcloud-gateway集成knife4j的示例詳解
- SpringCloud?Alibaba環(huán)境集成之nacos詳解
- Spring?Cloud?Ribbon?負(fù)載均衡使用策略示例詳解
- SpringCloud @RefreshScope刷新機(jī)制深入探究
- SpringCloud?@RefreshScope刷新機(jī)制淺析
- SpringCloud啟動(dòng)失敗問題匯總
- 一文吃透Spring?Cloud?gateway自定義錯(cuò)誤處理Handler
- SpringCloud Gateway路由組件詳解
- SpringCloud OpenFeign基本介紹與實(shí)現(xiàn)示例
- Spring Cloud Gateway替代zuul作為API網(wǎng)關(guān)的方法
- SpringCloud使用Feign實(shí)現(xiàn)遠(yuǎn)程調(diào)用流程詳細(xì)介紹
- SpringCloud修改Feign日志記錄級(jí)別過程淺析
- Spring?Cloud原理以及核心組件詳解
相關(guān)文章
Java線程安全和鎖Synchronized知識(shí)點(diǎn)詳解
在本篇文章里小編給大家分享的是關(guān)于Java線程安全和鎖Synchronized相關(guān)知識(shí)點(diǎn),有需要的朋友們可以參考下。2019-08-08Spring Boot使用GridFS實(shí)現(xiàn)文件的上傳和下載方式
這篇文章主要介紹了Spring Boot使用GridFS實(shí)現(xiàn)文件的上傳和下載方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Java設(shè)計(jì)模式之裝飾模式(Decorator模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之裝飾模式(Decorator模式)介紹,本文講解了為什么使用Decorator、如何使用裝飾模式、Jive中的Decorator實(shí)現(xiàn)等內(nèi)容,需要的朋友可以參考下2015-03-03Java Swing最詳細(xì)基礎(chǔ)知識(shí)總結(jié)
這篇文章主要介紹了Java Swing最詳細(xì)基礎(chǔ)知識(shí)總結(jié),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)Java Swing的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05Java?GenericObjectPool?對(duì)象池化技術(shù)之SpringBoot?sftp?連接池工具類詳解
這篇文章主要介紹了Java?GenericObjectPool?對(duì)象池化技術(shù)之SpringBoot?sftp?連接池工具類詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04