当我在授权标头中发送有效的不记名令牌时,为啥我的 Spring-Cloud Gateway / OAuth2-Client 没有通过身份验证?

Posted

技术标签:

【中文标题】当我在授权标头中发送有效的不记名令牌时,为啥我的 Spring-Cloud Gateway / OAuth2-Client 没有通过身份验证?【英文标题】:Why am I not getting authenticated by my Spring-Cloud Gateway / OAuth2-Client when I am sending my valid bearer-token in the authorization header?当我在授权标头中发送有效的不记名令牌时,为什么我的 Spring-Cloud Gateway / OAuth2-Client 没有通过身份验证? 【发布时间】:2021-04-30 10:47:07 【问题描述】:

我正在开发一个微服务基础设施,并首先实现一个 Spring Cloud Gateway 来代理我的所有请求。我通过 spring-boot-starter-oauth2-client 依赖项使用 keycloak 保护了我的网关。 我使用 TokenRelay 过滤器将 Bearer 附加到我的代理请求中。 我基本上关注了这个Blog 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

现在我正在将任何与 /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

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

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

@Configuration
@EnableWebSecurity
@RestController
public class SecurityConfig extends KeyCloakSecurityConfig 
    @GetMapping("/ping")
    public String ping() 
        return "hello";
    
    // ... resource server configuration in KeyCloakSecurityConfig


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

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

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

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

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

更新: 经过一番研究,我发现我的网关确实已经是无状态的,所以 sessioncookie 不是来自我的网关,而是来自 oauth2-client 依赖项(参见How to make API Gateway Stateless for Authentication/Authorization Process Using Oauth2?)。 所以我用一个简单的 Spring Boot 应用程序重现了我的问题,使用 keycloak 设置 OAuth2login 并暴露一个端点 /ping。 见https://github.com/smotastic/spring-oauth2-client-keycloak 如果您使用有效的 Bearer Access-Token 调用 /ping,应用程序会将您重定向到 keycloak 的登录页面,而不接受该令牌,因为它需要一个有效的 SESSION-Cookie

【问题讨论】:

【参考方案1】:

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

不幸的是,我无法使用 Keycloak (https://www.keycloak.org/docs/latest/securing_apps/#_spring_boot_adapter) 提供的官方 Spring-Boot-Adapter,因为该适配器具有一些 Web 依赖项,并且由于 spring-cloud-gateway 是基于 webflux 构建的,因此需要 Web 依赖项by keycloak 不能连用。

我的解决方案是,不再使用 spring-cloud-gateway,而是 spring-cloud-starter-netflix-zuul 网关。 这是建立在 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>

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

【讨论】:

你能不能在这里指导我 - ***.com/questions/67807642/…?

以上是关于当我在授权标头中发送有效的不记名令牌时,为啥我的 Spring-Cloud Gateway / OAuth2-Client 没有通过身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

Angular 中的不记名令牌

Firebase 授权不记名令牌未注册

带有 Zeep 和 Python 的 SOAP 客户端中的不记名令牌授权标头

为啥我们在授权头的token前面写Bearer?

为啥我们更喜欢授权标头将承载令牌发送到服务器而不是 URL 编码等其他技术

401 未经授权使用带有 Laravel Passport 的不记名令牌