SpringCloud開啟session共享并存儲到Redis的實現(xiàn)
備注:以下所有的gateway均指SpringCloud Gateway
一、原架構(gòu)
前端<->gateway<->console后端
原來session是在console-access中維護的,當中間有了一層gateway之后,gateway會認為session變了,從而將session的cookie信息重置,導致無法在前端的后續(xù)請求無法將cookie帶上來
如下圖所示的spring-web的代碼中這個state會變成State.NEW而非State.STARTED

在這種情況下,部署的時候只有跳過gateway才能正常進行
即按照如下方式才能進行session的判斷
前端<->console后端
二、調(diào)整架構(gòu)以及相應的代碼

整個業(yè)務處理和原來的沒有任何改變
將session的判斷控制挪到gateway當中
首先將console后端中登錄以及后續(xù)業(yè)務當中涉及到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();
//以下四項配置主要用于跨域調(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方法 對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) {
//如果轉(zhuǎn)不了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。開發(fā)的時候可以配置為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和端口轉(zhuǎn)發(fā)到http://127.0.0.1:30000對應的服務(確保該服務中不存在/ccuconsole開頭的路徑)中
2、跨域
對于跨域的部必須使用https(現(xiàn)在的瀏覽器版本的要求,瀏覽器已不再支持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轉(zhuǎn)發(fā)到http://127.0.0.1:30000對應的服務(確保該服務中有無/ccuconsole開頭的路徑并不影響,但是為了可切換同域部署,不推薦存在/ccuconsole開頭的路徑)中
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- SpringCloud Hystrix熔斷器使用方法介紹
- SpringCloud Gateway動態(tài)路由配置詳解
- Spring?Cloud?Gateway遠程命令執(zhí)行漏洞分析(CVE-2022-22947)
- SpringSecurit鹽值加密的密碼驗證以及強密碼驗證過程
- Spring?Cloud?Alibaba實現(xiàn)服務的無損下線功能(案例講解)
- springcloud-gateway集成knife4j的示例詳解
- SpringCloud?Alibaba環(huán)境集成之nacos詳解
- Spring?Cloud?Ribbon?負載均衡使用策略示例詳解
- SpringCloud @RefreshScope刷新機制深入探究
- SpringCloud?@RefreshScope刷新機制淺析
- SpringCloud啟動失敗問題匯總
- 一文吃透Spring?Cloud?gateway自定義錯誤處理Handler
- SpringCloud Gateway路由組件詳解
- SpringCloud OpenFeign基本介紹與實現(xiàn)示例
- Spring Cloud Gateway替代zuul作為API網(wǎng)關(guān)的方法
- SpringCloud使用Feign實現(xiàn)遠程調(diào)用流程詳細介紹
- SpringCloud修改Feign日志記錄級別過程淺析
- Spring?Cloud原理以及核心組件詳解
相關(guān)文章
Spring Boot使用GridFS實現(xiàn)文件的上傳和下載方式
這篇文章主要介紹了Spring Boot使用GridFS實現(xiàn)文件的上傳和下載方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Java?GenericObjectPool?對象池化技術(shù)之SpringBoot?sftp?連接池工具類詳解
這篇文章主要介紹了Java?GenericObjectPool?對象池化技術(shù)之SpringBoot?sftp?連接池工具類詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04

