【问题标题】:Spring security does not generate session cookie for postman but does for chromeSpring Security 不会为邮递员生成会话 cookie,但会为 chrome 生成会话 cookie
【发布时间】:2022-02-02 12:20:52
【问题描述】:

我有 Spring Cloud Gateway 使用 KeyCloak 作为 OpenId 连接提供程序。如果我使用 Postman 在网关(而不是某些后端服务)上点击 REST 端点,当我点击端点时它不会生成会话 Cookie。但是,如果我使用 Chrome 浏览器访问同一端点,则对该端点的响应会生成一个会话 cookie。因此,请求缓存不适用于 Postman,但适用于 chrome。我还没有尝试在后端访问 REST 端点,但尚不清楚为什么它可以与浏览器一起使用,而不是与 Postman(Linux 客户端)一起使用。 keycloak 身份验证部分工作正常。

例如当我尝试使用邮递员访问 localhost:8080/live 时,不会为该请求生成会话 cookie,并且用户代理被重新定向到 localhost:8080/oauth2/authorization/keycloak-registration(在同一个网关服务上)。然后此端点重定向到 keycloak(授权端点)并设置会话 cookie。但是,由于此端点忽略了原始请求,因此不会发生请求缓存;它只是在后续身份验证成功时重定向到网关上的“/”。

OTOH,如果我使用 chrome,我的原始请求(到 /live)会生成一个会话 cookie 并重定向到 localhost:8080/oauth2/authorization/keycloak-registration 不会生成任何额外的会话 cookie。与 keycloak 的后续身份验证交互工作正常并且请求缓存工作正常。

我的 Spring Security 配置如下。有趣的是,与 Spring MVC 中的 HttpSecurity 不同,ServerHttpSecurity 没有提供任何用于会话管理的旋钮。所以,我不知道如何以一种确定的方式使用户代理的行为一致。

@Configuration
@EnableWebFluxSecurity
public class AndurilSecurityConfig {
    
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
            .oauth2Login(withDefaults())
            .requestCache();
        
        http.csrf().disable();

        
        return http.build();
    }
}

application.yml 的相关摘录,带有一些编辑,

spring.cloud.gateway:
  httpclient:
    wiretap: true
  httpserver:
    wiretap: true
  default-filters:
  - name: BasicAuthFilter
  routes:
    - id: adminservice
      uri: http://${ADMIN_SERVICE}/
      predicates:
        - Path=/admin/**
    - id: appservice
      uri: http://${APP_SERVICE}/
      predicates:
        - Path=/app/**
spring.security.oauth2.client:
  provider:
    keycloak:
      issuer-uri: http://${AUTHSERVER}/auth/realms/${REALM}
      user-name-attribute: preferred_username
  registration:
    keycloak-registration:
      provider: keycloak
      client-id: ${CLIENT_ID}
      client-secret: ${CLIENT_SECRET}
      authorization-grant-type: authorization_code
      redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"

似乎问题出在 MediaTypes 上。出于某种原因,*/* 被拒绝/忽略,但text/html 工作正常。目前尚不清楚为什么 MediaTypes 会在请求缓存中发挥作用。

WITH CHROME:
-------------------------------------------
03:59:49.049 reactor-http-epoll-2 DEBUG anduril NegatedServerWebExchangeMatcher: matches = true
03:59:49.049 reactor-http-epoll-2 DEBUG anduril AndServerWebExchangeMatcher: Trying to match using MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[*/*]]
03:59:49.049 reactor-http-epoll-2 DEBUG anduril MediaTypeServerWebExchangeMatcher: httpRequestMediaTypes=[text/html, application/xhtml+xml, image/avif, image/webp, application/xml;q=0.9, */*;q=0.8]
03:59:49.049 reactor-http-epoll-2 DEBUG anduril MediaTypeServerWebExchangeMatcher: Processing text/html
03:59:49.049 reactor-http-epoll-2 DEBUG anduril MediaTypeServerWebExchangeMatcher: text/html .isCompatibleWith text/html = true
03:59:49.049 reactor-http-epoll-2 DEBUG anduril AndServerWebExchangeMatcher: All requestMatchers returned true
03:59:49.049 reactor-http-epoll-2 DEBUG anduril WebSessionServerRequestCache: Request added to WebSession: '/live'
03:59:49.049 reactor-http-epoll-2 DEBUG anduril DefaultServerRedirectStrategy: Redirecting to '/oauth2/authorization/keycloak-registration'
03:59:49.049 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Checking match of request : '/oauth2/authorization/keycloak-registration'; against '/oauth2/authorization/{registrationId}'



WITH POSTMAN USING */*:
-------------------------------------------
04:30:44.044 parallel-3 DEBUG anduril NegatedServerWebExchangeMatcher: matches = true
04:30:44.044 parallel-3 DEBUG anduril AndServerWebExchangeMatcher: Trying to match using MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[*/*]]
04:30:44.044 parallel-3 DEBUG anduril MediaTypeServerWebExchangeMatcher: httpRequestMediaTypes=[*/*]
04:30:44.044 parallel-3 DEBUG anduril MediaTypeServerWebExchangeMatcher: Processing */*
04:30:44.044 parallel-3 DEBUG anduril MediaTypeServerWebExchangeMatcher: Ignoring
04:30:44.044 parallel-3 DEBUG anduril MediaTypeServerWebExchangeMatcher: Did not match any media types
04:30:44.044 parallel-3 DEBUG anduril AndServerWebExchangeMatcher: Did not match
04:30:44.044 parallel-3 DEBUG anduril DefaultServerRedirectStrategy: Redirecting to '/oauth2/authorization/keycloak-registration'
04:30:44.044 reactor-http-epoll-4 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Checking match of request : '/oauth2/authorization/keycloak-registration'; against '/oauth2/authorization/{registrationId}'



WITH POSTMAN USING text/html:
-------------------------------------------
04:47:30.030 parallel-1 DEBUG anduril NegatedServerWebExchangeMatcher: matches = true
04:47:30.030 parallel-1 DEBUG anduril AndServerWebExchangeMatcher: Trying to match using MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[*/*]]
04:47:30.030 parallel-1 DEBUG anduril MediaTypeServerWebExchangeMatcher: httpRequestMediaTypes=[text/html]
04:47:30.030 parallel-1 DEBUG anduril MediaTypeServerWebExchangeMatcher: Processing text/html
04:47:30.030 parallel-1 DEBUG anduril MediaTypeServerWebExchangeMatcher: text/html .isCompatibleWith text/html = true
04:47:30.030 parallel-1 DEBUG anduril AndServerWebExchangeMatcher: All requestMatchers returned true
04:47:30.030 parallel-1 DEBUG anduril WebSessionServerRequestCache: Request added to WebSession: '/live'
04:47:30.030 parallel-1 DEBUG anduril DefaultServerRedirectStrategy: Redirecting to '/oauth2/authorization/keycloak-registration'
04:47:30.030 reactor-http-epoll-3 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Checking match of request : '/oauth2/authorization/keycloak-registration'; against '/oauth2/authorization/{registrationId}'

【问题讨论】:

  • 我想回答你的问题,但我倒读了,所以我删除了答案。您使用什么主机与 keycloak 交互?是localhost,还是外部主机?
  • 嗨,史蒂夫,感谢您对此进行调查。我正在使用泊坞窗(撰写)。 Spring Cloud Gateway 和 Keycloak 都作为容器运行。但是,网关将 keycloak 视为外部主机(使用 docker-compose 中的 extra_host 字段)。整个设置(包括邮递员和浏览器)在我的 linux 桌面上运行。我使用我的 linux 桌面的 /etc/hosts 中的一个条目来 DNS 解析 keycloak 主机名。尚不清楚为什么 postman 和 chrome 之间的行为会有所不同。
  • 你能提供你的spring cloud gateway配置吗?我的猜测是路由的配置方式存在问题,或者网关后面不同主机设置的 cookie 之间存在冲突,但如果不知道这些细节就很难判断。
  • 嗨史蒂夫。我已经添加了 SCG 配置。 BasicAuthFilter 用于无状态后端服务的基本身份验证;这最终将被 OAuth2 访问令牌取代。目前,我只是使用 KeyCloak 进行身份验证。此外,我正在访问/测试 SCG 本地的端点(而不是后端)。这个本地端点(例如 localhost:/live)是我访问的第一个 URL。谢谢。
  • 对不起,我没看到你在哪里添加的。

标签: spring spring-boot spring-security spring-cloud-gateway


【解决方案1】:

根据您的配置,我能够排除与 Spring Cloud Gateway 和 OAuth 2.0 支持相关的任何内容。这是默认请求缓存实现WebSessionServerRequestCache 的一种更微妙的行为。

TL;DR — 如果您将 Accept: text/html 添加到 Postman 的标题中,它应该可以工作。

Spring Security 的 webflux 支持中的请求缓存被设计为(类似于 servlet)仅适用于来自浏览器的 Web 请求。由于 Postman 不完全是浏览器,因此它不属于 Spring Security 开箱即用支持的情况,这就解释了为什么您的行为在 Postman 中有所不同。像 curl 这样的命令行客户端也属于这一类,原生应用等也属于这一类。

邮递员默认发送*/*Accept 标头,我假设是为了尝试说“给我任何回应”。 Spring Security 忽略了这一点,并进一步特别期望 Accept 标头为 text/html,这是在典型的浏览器请求中发送的。见these lines

【讨论】:

  • 感谢您的指点,史蒂夫;这真的很有帮助。 (默认)请求缓存策略是有意义的。知道吗,生成会话 cookie 的策略是什么?
  • 我认为这是请求缓存的副作用,除其他外(例如保存 csrf 令牌)。
  • 真正的史蒂夫。如果没有要记住的内容,则无需生成状态/会话。 :)
  • 顺便说一句,我看到了 SCG 和 Spring Security 的其他问题。我可以请他们注意吗? stackoverflow.com/questions/70990403/…。我认为乔什·卡明斯(Josh Cummings)正在查看它,但可能是他在周末错过了我的回复。
  • 如果对您有帮助,您介意投票和/或接受答案吗?我相信乔希会在他有时间的时候完成它。我们通常不会在周末检查 SO,但我碰巧所以我想我会回复。
猜你喜欢
  • 1970-01-01
  • 2017-11-23
  • 1970-01-01
  • 2012-05-23
  • 2014-11-03
  • 2012-02-09
  • 2018-11-14
  • 1970-01-01
相关资源
最近更新 更多