使用 Spring Security OAuth 时 Spring Boot Reactive WebClient “serverWebExchange must be null”

Posted

技术标签:

【中文标题】使用 Spring Security OAuth 时 Spring Boot Reactive WebClient “serverWebExchange must be null”【英文标题】:Spring Boot Reactive WebClient "serverWebExchange must be null" when Spring Security OAuth is in use 【发布时间】:2019-11-20 05:39:05 【问题描述】:

我想从使用 Spring Security OAuth 2 客户端凭据设置的 Spring Boot WebFlux 应用程序中使用 WebClient。

但是,我得到:java.lang.IllegalArgumentException: serverWebExchange must be null

代码在这里:https://github.com/mparaz/spring-apigee-client

当我通过从pom.xml 中删除 Spring Security 来禁用它时,它可以正常工作。

当我继续使用 Spring Security 时,不是将 webClient() 链结果返回给控制器,而是将其打印出来,它也可以工作。

使用 Spring Security 时,Reactive 客户端和服务器似乎无法一起工作。我怎样才能让它们一起运行?

【问题讨论】:

您已经使用未经身份验证的 repo/exchange 设置了 webclient。本质上,它期望对 Reactive servlet 上下文持有者进行 null 或匿名身份验证。当它拨打电话时 感谢@DarrenForsythe。您能否建议我应该在 github.com/mparaz/spring-apigee-client/blob/develop/src/main/… 中做些什么?而不是: ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository()); 应该能够自动连接ServerOAuth2AuthorizedClientRepository,只有两个具体的 impl 可用。 UnAuthenticated 是另一个没有关联主体的请求 我遇到了完全相同的问题。当我通过单元测试调用受 oauth2 客户端凭据保护的资源时,一切都很好。但是当我通过未经身份验证的休息请求调用它时,它似乎传播了反应式 servlet 上下文持有者,而不是创建一个新的。 【参考方案1】:

似乎如果您使用UnAuthenticatedServerOAuth2AuthorizedClientRepository,它会将webExchange从源请求传播到您的@RestController,然后传播到您用来调用其他服务的WebClient,从而导致java.lang.IllegalArgumentException: serverWebExchange must be null

要解决此问题,请使用 ServerOAuth2AuthorizedClientRepository 的自动装配实现(这恰好是 AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder(ReactiveClientRegistrationRepository clientRegistrations,
                                                          ObjectMapper objectMapper,
                                                          ServerOAuth2AuthorizedClientRepository clientRepository) 

        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2ClientFilter = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
                clientRegistrations,
                clientRepository);
        oauth2ClientFilter.setDefaultClientRegistrationId("apigee");
        WebClient.Builder builder = WebClient.builder();
        builder.defaultHeader("Content-Type", MediaType.APPLICATION_JSON.toString());
        builder.defaultHeader("Accept", MediaType.APPLICATION_JSON.toString());
        builder.filter(oauth2ClientFilter);
        return builder;
    

【讨论】:

【参考方案2】:

对我来说,问题出在

https://docs.spring.io/spring-security/site/docs/5.5.x/reference/html5/#oauth2Client-authorized-manager-provider

The DefaultOAuth2AuthorizedClientManager is designed to be used within the context of a HttpServletRequest. When operating outside of a HttpServletRequest context, use AuthorizedClientServiceOAuth2AuthorizedClientManager instead.

对以下链接的评论对我有用

https://www.gitmemory.com/issue/spring-projects/spring-security/8444/621567261

另一个链接

https://github.com/spring-projects/spring-security/issues/8230

评论

DefaultReactiveOAuth2AuthorizedClientManager is intended to be used within a request context.

Given that you're seeing serverWebExchange cannot be null, you must be operating outside of a request context, which in case you should use AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager instead.

NOTE: Change the ServerOAuth2AuthorizedClientRepository parameter to ReactiveOAuth2AuthorizedClientService.

实际代码

    @Bean
    fun serverOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations: List<ClientRegistration>)
            : ServerOAuth2AuthorizedClientExchangeFilterFunction 
        val clientRegistrationRepository = InMemoryReactiveClientRegistrationRepository(clientRegistrations)
        val authorizedClientService = InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository)

        val oAuth2AuthorizedClientManager = AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository,
            authorizedClientService
        )
        val filterFunction = ServerOAuth2AuthorizedClientExchangeFilterFunction(oAuth2AuthorizedClientManager)
        filterFunction.setDefaultClientRegistrationId(clientId)
        return filterFunction
    

【讨论】:

ServerOAuth2AuthorizedClientExchangeFilterFunction 构造函数需要 ReactiveOAuth2AuthorizedClientManager 类型。您的“实际代码”无法编译。

以上是关于使用 Spring Security OAuth 时 Spring Boot Reactive WebClient “serverWebExchange must be null”的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security/OAuth 如何找出 AuthenticationPrincipal

如何使用 redis 使用 spring-security-oauth2 持久化令牌

如何使用 XML 使用 Spring Security Oauth2 启用 /oauth/check_token

弃用 spring-security-oauth 作为客户端

spring-security-oauth2中的HttpSecurity配置问题

使用 spring-security-oauth2 2.0.5.RELEASE 保护 Spring REST 服务