当我在授权标头中发送我的有效承载令牌时,为什么我没有通过我的 Spring-Cloud Gateway/OAuth2-Client 进行身份验证?

smo*_*tic 5 spring spring-security oauth-2.0 microservices spring-oauth2

我正在开发一个微服务基础设施,并首先实现一个 Spring Cloud Gateway 来代理我的所有请求。我通过spring-boot-starter-oauth2-client Dependency使用 keycloak 保护了我的网关。我使用 TokenRelay 过滤器将承载附加到我的代理请求。我基本上跟着这个博客https://blog.jdriven.com/2019/11/spring-cloud-gateway-with-openid-connect-and-token-relay/

spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8090/auth/realms/testrealm
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
spring.security.oauth2.client.registration.keycloak.client-id=yyy
spring.security.oauth2.client.registration.keycloak.client-secret=xxx
spring.cloud.gateway.default-filters[0]=TokenRelay
Run Code Online (Sandbox Code Playgroud)

现在我正在路由任何匹配 /ping 到我的 Ping 微服务的请求。

spring.cloud.gateway.routes[0].id=some-id
spring.cloud.gateway.routes[0].uri=http://localhost:8079
spring.cloud.gateway.routes[0].predicates[0]=Path=/ping
Run Code Online (Sandbox Code Playgroud)

Ping 微服务是一个spring-boot-starter-oauth2-resource-server,并如此配置,并且只公开一个测试端点 /ping。

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8090/auth/realms/testrealm

Run Code Online (Sandbox Code Playgroud)
@Configuration
@EnableWebSecurity
@RestController
public class SecurityConfig extends KeyCloakSecurityConfig {
    @GetMapping("/ping")
    public String ping() {
        return "hello";
    }
    // ... resource server configuration in KeyCloakSecurityConfig
}

Run Code Online (Sandbox Code Playgroud)

现在我访问我的网关(它在 8765 上运行),通过 http://localhost:8765/ping 我被正确重定向到 keycloak 的登录页面。我使用我的 testuser 登录,并被重定向到我的网关,然后它将我的请求代理到 Ping 微服务。PingMicroService 再次针对 Keycloak 验证 AccessToken,我收到了“你好”。

但是,如果我想用 Postman 等测试我的 API,我会假设,我可以在调用我的 /ping 端点之前将 Bearer-Token 添加到 Authorization Header 中。所以我使用令牌端点http://localhost:8090/auth/realms/testrealm/protocol/openid-connect/token获取我的访问令牌,并将其复制到我的网关请求的授权标头中。 但是发送后,我得到了登录页面,而不是请求的资源。 在此处输入图片说明

当我在授权标头中发送我的有效承载令牌时,为什么我的网关没有通过身份验证?

当我直接调用我的资源服务器时,承载令牌被接受。这是有道理的,因为 api 网关除了将我的访问令牌中继到资源服务器之外没有做任何其他事情,因此他可以进行必要的令牌自省。

经过一些研究,我发现当我通过浏览器进行 api 调用时,Spring 实际上向我发送了一个 SESSION 作为 cookie。当我将此 SESSION 作为 cookie 复制到邮递员的请求中时,一切正常。是否有一些关于为什么 Spring 使用 SESSION 的文档,或者这基本上是 Auth-Code,因为我们在这里使用授权代码流?

更新:经过一些研究,我发现我的网关确实已经是无状态的,所以 sessioncookie 不是来自我的网关,而是来自 oauth2-client 依赖项(请参阅如何使用 Oauth2 使 API 网关无状态进行身份验证/授权过程?)。所以我用一个简单的 Spring Boot 应用程序重现了我的问题,使用 keycloak 设置 OAuth2login 并公开一个端点 /ping。见https://github.com/smotastic/spring-oauth2-client-keycloak 如果你使用有效的 Bearer Access-Token 调用 /ping,应用程序会将你重定向到 keycloak 的登录页面,不接受令牌,因为它需要一个有效的 SESSION-Cookie

smo*_*tic 4

所以对于任何有类似问题的人来说。问题出在 spring-boot-starter-oauth2-client 依赖项中。通过从授权服务器发回 SESSION-Cookie 而不是访问令牌,这使我的网关成为有状态的。

不幸的是,我无法使用 Keycloak ( https://www.keycloak.org/docs/latest/securing_apps/#_spring_boot_adapter ) 提供的官方 Spring-Boot-Adapter,因为该适配器有一些 Web 依赖项,并且作为 spring- cloud-gateway 构建于 webflux 之上,keycloak 所需的 web 依赖无法结合使用。

我的解决方案是,不再使用 spring-cloud-gateway ,而是使用spring-cloud-starter-netflix-zuul gateway。这是基于 web 构建的,而不是基于 webflux,因此我能够使用 keycloak 的官方 Spring-Boot-Adapter。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
Run Code Online (Sandbox Code Playgroud)

Zuul 还发送 JSessionId,但仍然接受请求标头中的 accesstoken(如果设置)作为有效授权。

请注意,Zuul 已置于维护模式(https://cloud.spring.io/spring-cloud-netflix/multi/multi__modules_in_maintenance_mode.html)。所以它不会收到任何新功能,建议使用 spring-cloud-gateway 代替。但对于我的用例来说,这是不可行的,所以如果 oauth2-client 发布更新,我将来可能会回到这个网关,我也可以在其中进行无状态身份验证。

这是一个示例存储库,它使用 zuul-gateway,使用官方 Spring-Boot-Keycloak-Adapter 受 keycloak 保护

https://github.com/smo-snippets/spring-cloud-microservices/tree/master/api-gateway