【发布时间】: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