【问题标题】:Cookies path with Spring Cloud GatewaySpring Cloud Gateway 的 Cookie 路径
【发布时间】:2019-09-16 02:10:47
【问题描述】:

考虑使用 Spring Boot 2.1.2Spring Cloud Greenwich.RELEASE 的基于 微服务 的应用程序:

  • 每个微服务都使用 JSESSIONID cookie 来标识自己的专用 Servlet 会话(即没有与 Spring Session 和 Redis 共享的全局唯一会话)。
  • 外部传入请求由 Spring Cloud Gateway 路由(以及通过 Spring Cloud Netflix 使用的 Eureka 注册表,但这应该不相关)。

当 Spring Cloud Gateway 返回微服务响应时,它按原样返回“Set-Cookie”,即具有相同的“/”路径。

当客户端调用第二个微服务时,来自第一个微服务的 JSESSIONID 被转发但被忽略(因为对应的会话只存在于第一个微服务中)。所以第二个微服务会返回一个新的 JSESSIONID。因此,第一个会话丢失。

总之,每次调用不同的微服务都会丢失之前的会话

我希望使用 Spring Cloud Gateway 进行一些 cookie 路径转换,但在文档中没有找到这样的功能。谷歌也不走运。

我们如何解决这个问题(我可能错过了一个配置参数,一个 API 来编写这样的 cookie 路径翻译等)?

【问题讨论】:

  • 没有粘滞会话支持。我认为您可以重写所有 cookie 的标头。
  • 是的 GlobalFilter 似乎在这里有帮助。
  • 不幸的是,jsessionid 被基础设施双重使用,并在 PCF 环境中被覆盖。我们解决了您描述的粘性会话问题,如 spencergibb 所说,在标题中重写 cookie 名称。

标签: spring cookies spring-cloud jsessionid spring-cloud-gateway


【解决方案1】:

我遇到了同样的问题,并使用 Spring Boot 2.5.4 和 Spring Cloud Gateway 2020.0.3 找到了以下解决方案:

为了独立于下游服务的 Cookie 命名,我决定重命名所有通过网关的 cookie。但为了避免下游请求(来自网关本身)中出现重复的会话 cookie,我还重命名了网关 cookie。

重命名网关会话 Cookie

很遗憾,customizing the gateway cookie name 使用 server.servlet.session.cookie.name 在当前网关版本中无法使用。

因此,注册一个自定义 WebSessionManager bean(需要的名称,因为自动配置取决于 bean 名称!)更改 cookie 名称(使用您喜欢的任何名称,除了典型的会话 cookie 名称,如 SESSIONJSESSION_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;
}

重命名创建的 Cookies

下一步是重命名下游服务器设置的(所有)cookie。这很容易,因为有一个RewriteResponseHeader 过滤器可用。我决定简单地为每个 cookie 名称添加一个前缀(为每个下游选择一个唯一的):

filters:
  - "RewriteResponseHeader=Set-Cookie, ^([^=]+)=, DS1_$1="

重命名发送的 Cookies

最后一步是在发送到下游服务器之前重命名 cookie。由于下游服务器的每一个cookie都有唯一的前缀,去掉前缀即可:

filters:
  - "RewriteRequestHeader=Cookie, ^DS1_([^=]+)=, $1="

Arg,currently there is no such filter available。但是基于现有的RewriteResponseHeader 过滤器,这很容易(如果您将其注册为 bean,Cloud Gateway 将使用它):

@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())));
    }
}

【讨论】:

    【解决方案2】:

    我没有在 GlobalFilter 中更改 JSESSIONID cookie 路径,而是在 application.yml 中更改了 cookie 的名称:

    # Each microservice uses its own session cookie name to prevent conflicts
    server.servlet.session.cookie.name: JSESSIONID_${spring.application.name}
    

    【讨论】:

      【解决方案3】:

      只需在网关项目中将 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);
          }
      

      【讨论】:

      • 很遗憾,这无助于区分各种微服务的 cookie。
      • @FlorianBeaufumé 是的,在我的情况下不需要区分,能否告诉我你的场景,同一个浏览器客户端需要登录多个微服务?
      • 是的,就我而言,浏览器需要为每个微服务使用一个会话。
      猜你喜欢
      • 2023-01-21
      • 2018-07-15
      • 2020-12-15
      • 1970-01-01
      • 2019-06-10
      • 2020-06-01
      • 2019-08-03
      • 2019-03-31
      • 2021-08-07
      相关资源
      最近更新 更多