Spring 云网关的 Cookie 路径
Cookies path with Spring Cloud Gateway
考虑使用 Spring Boot 2.1.2 和 [=48= 的基于 微服务 的应用程序]云Greenwich.RELEASE:
- 每个微服务使用 JSESSIONID cookie 来识别它自己的 专用 Servlet 会话 (即没有与 [=48= 共享的全局唯一会话) ] 会话和 Redis).
- 外部传入请求由 Spring 云网关 路由(以及通过 Spring Cloud Netflix 使用的 Eureka 注册中心,但这应该不相关) .
当 Spring 云网关 return 是微服务响应时,它 return 是 "Set-Cookie" 原样,即具有相同的“/”路径。
当客户端调用第二个微服务时,来自第一个微服务的 JSESSIONID 被转发但被忽略(因为相应的会话仅存在于第一个微服务中)。所以第二个微服务将 return 一个新的 JSESSIONID。结果第一个会话丢失了。
总而言之,每次调用不同的微服务都会丢失前一个会话。
我希望使用 Spring Cloud Gateway 进行一些 cookie 路径转换,但在文档中没有发现此类功能。 Google.
也不走运
我们如何解决这个问题(我可能错过了一个配置参数,一个
API要写这样的cookies路径翻译等)?
只需在网关项目中将 cookie 名称重置为 GATEWAY_SESSION 即可避免会话冲突:
@Autowired(required = false)
public void setCookieName(HttpHandler httpHandler) {
if (httpHandler == null) return;
if (!(httpHandler instanceof HttpWebHandlerAdapter)) return;
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
CookieWebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver();
sessionIdResolver.setCookieName("GATEWAY_SESSION");
sessionManager.setSessionIdResolver(sessionIdResolver);
((HttpWebHandlerAdapter) httpHandler).setSessionManager(sessionManager);
}
我没有在 GlobalFilter 中更改 JSESSIONID cookies 路径,而是在 application.yml
:
中更改了 cookie 的名称
# Each microservice uses its own session cookie name to prevent conflicts
server.servlet.session.cookie.name: JSESSIONID_${spring.application.name}
我遇到了同样的问题,并使用 Spring Boot 2.5.4 和 Spring Cloud Gateway[= 找到了以下解决方案44=] 2020.0.3:
为了独立于下游服务的Cookie命名,我决定在通过网关的途中重命名所有cookie。但是为了避免下游请求中出现重复的会话 cookie(来自网关本身),我还重命名了网关 cookie。
重命名网关会话 Cookie
不幸的是,customizing the gateway cookie name 使用 server.servlet.session.cookie.name
无法使用当前的网关版本。
因此注册一个自定义 WebSessionManager
bean(自动配置所需的名称以 bean 名称为条件!)更改 cookie 名称(使用任何你喜欢的名称,除了典型的会话 cookie 名称,如 SESSION
, JSESSION_ID
, …):
static final String SESSION_COOKIE_NAME = "GATEWAY_SESSION";
@Bean(name = WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
WebSessionManager webSessionManager(WebFluxProperties webFluxProperties) {
DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();
CookieWebSessionIdResolver webSessionIdResolver = new CookieWebSessionIdResolver();
webSessionIdResolver.setCookieName(SESSION_COOKIE_NAME);
webSessionIdResolver.addCookieInitializer((cookie) -> cookie
.sameSite(webFluxProperties.getSession().getCookie().getSameSite().attribute()));
webSessionManager.setSessionIdResolver(webSessionIdResolver);
return webSessionManager;
}
重命名创建的 Cookie
下一步是重命名下游服务器设置的(所有)cookie。这很容易,因为有一个 RewriteResponseHeader
过滤器可用。我决定简单地为每个 cookie 名称添加一个前缀(为每个下游选择一个唯一的前缀):
filters:
- "RewriteResponseHeader=Set-Cookie, ^([^=]+)=, DS1_="
重命名发送的 Cookie
最后一步是在发送到下游服务器之前重命名 cookie。由于下游服务器的每个cookie都有唯一的前缀,去掉前缀即可:
filters:
- "RewriteRequestHeader=Cookie, ^DS1_([^=]+)=, ="
参数,currently there is no such filter available。但是基于现有的 RewriteResponseHeader
过滤器,这很容易(如果您将其注册为 bean,云网关将使用它):
@Component
class RewriteRequestHeaderGatewayFilterFactory extends RewriteResponseHeaderGatewayFilterFactory
{
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> rewriteHeaders(httpHeaders, config)).build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return filterToStringCreator(RewriteRequestHeaderGatewayFilterFactory.this)
.append("name", config.getName()).append("regexp", config.getRegexp())
.append("replacement", config.getReplacement()).toString();
}
};
}
private void rewriteHeaders(HttpHeaders httpHeaders, Config config)
{
httpHeaders.put(config.getName(), rewriteHeaders(config, httpHeaders.get(config.getName())));
}
}
考虑使用 Spring Boot 2.1.2 和 [=48= 的基于 微服务 的应用程序]云Greenwich.RELEASE:
- 每个微服务使用 JSESSIONID cookie 来识别它自己的 专用 Servlet 会话 (即没有与 [=48= 共享的全局唯一会话) ] 会话和 Redis).
- 外部传入请求由 Spring 云网关 路由(以及通过 Spring Cloud Netflix 使用的 Eureka 注册中心,但这应该不相关) .
当 Spring 云网关 return 是微服务响应时,它 return 是 "Set-Cookie" 原样,即具有相同的“/”路径。
当客户端调用第二个微服务时,来自第一个微服务的 JSESSIONID 被转发但被忽略(因为相应的会话仅存在于第一个微服务中)。所以第二个微服务将 return 一个新的 JSESSIONID。结果第一个会话丢失了。
总而言之,每次调用不同的微服务都会丢失前一个会话。
我希望使用 Spring Cloud Gateway 进行一些 cookie 路径转换,但在文档中没有发现此类功能。 Google.
也不走运我们如何解决这个问题(我可能错过了一个配置参数,一个 API要写这样的cookies路径翻译等)?
只需在网关项目中将 cookie 名称重置为 GATEWAY_SESSION 即可避免会话冲突:
@Autowired(required = false)
public void setCookieName(HttpHandler httpHandler) {
if (httpHandler == null) return;
if (!(httpHandler instanceof HttpWebHandlerAdapter)) return;
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
CookieWebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver();
sessionIdResolver.setCookieName("GATEWAY_SESSION");
sessionManager.setSessionIdResolver(sessionIdResolver);
((HttpWebHandlerAdapter) httpHandler).setSessionManager(sessionManager);
}
我没有在 GlobalFilter 中更改 JSESSIONID cookies 路径,而是在 application.yml
:
# Each microservice uses its own session cookie name to prevent conflicts
server.servlet.session.cookie.name: JSESSIONID_${spring.application.name}
我遇到了同样的问题,并使用 Spring Boot 2.5.4 和 Spring Cloud Gateway[= 找到了以下解决方案44=] 2020.0.3:
为了独立于下游服务的Cookie命名,我决定在通过网关的途中重命名所有cookie。但是为了避免下游请求中出现重复的会话 cookie(来自网关本身),我还重命名了网关 cookie。
重命名网关会话 Cookie
不幸的是,customizing the gateway cookie name 使用 server.servlet.session.cookie.name
无法使用当前的网关版本。
因此注册一个自定义 WebSessionManager
bean(自动配置所需的名称以 bean 名称为条件!)更改 cookie 名称(使用任何你喜欢的名称,除了典型的会话 cookie 名称,如 SESSION
, JSESSION_ID
, …):
static final String SESSION_COOKIE_NAME = "GATEWAY_SESSION";
@Bean(name = WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
WebSessionManager webSessionManager(WebFluxProperties webFluxProperties) {
DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();
CookieWebSessionIdResolver webSessionIdResolver = new CookieWebSessionIdResolver();
webSessionIdResolver.setCookieName(SESSION_COOKIE_NAME);
webSessionIdResolver.addCookieInitializer((cookie) -> cookie
.sameSite(webFluxProperties.getSession().getCookie().getSameSite().attribute()));
webSessionManager.setSessionIdResolver(webSessionIdResolver);
return webSessionManager;
}
重命名创建的 Cookie
下一步是重命名下游服务器设置的(所有)cookie。这很容易,因为有一个 RewriteResponseHeader
过滤器可用。我决定简单地为每个 cookie 名称添加一个前缀(为每个下游选择一个唯一的前缀):
filters:
- "RewriteResponseHeader=Set-Cookie, ^([^=]+)=, DS1_="
重命名发送的 Cookie
最后一步是在发送到下游服务器之前重命名 cookie。由于下游服务器的每个cookie都有唯一的前缀,去掉前缀即可:
filters:
- "RewriteRequestHeader=Cookie, ^DS1_([^=]+)=, ="
参数,currently there is no such filter available。但是基于现有的 RewriteResponseHeader
过滤器,这很容易(如果您将其注册为 bean,云网关将使用它):
@Component
class RewriteRequestHeaderGatewayFilterFactory extends RewriteResponseHeaderGatewayFilterFactory
{
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> rewriteHeaders(httpHeaders, config)).build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return filterToStringCreator(RewriteRequestHeaderGatewayFilterFactory.this)
.append("name", config.getName()).append("regexp", config.getRegexp())
.append("replacement", config.getReplacement()).toString();
}
};
}
private void rewriteHeaders(HttpHeaders httpHeaders, Config config)
{
httpHeaders.put(config.getName(), rewriteHeaders(config, httpHeaders.get(config.getName())));
}
}