You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将非默认用户的OAuth身份关联到WebClient?

问题

我正在构建一个服务,允许用户将多个OAuth身份关联到其账户,然后基于任一/所有身份检索信息。我使用Spring提供的R2dbcReactiveOAuth2AuthorizedClientService将OAuth身份存储在Postgres中,当前的挑战是将已保存的OAuth身份关联到WebClient,以便基于该OAuth身份获取信息。

根据ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient的JavaDoc,可以传入OAuth2AuthorizedClient,它会在WebClient.retrieve()时使用该身份:

修改ClientRequest.attributes()以包含用于提供Bearer Token的OAuth2AuthorizedClient。示例用法:

WebClient webClient = WebClient.builder()
    .filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager))
    .build();
Mono<String> response = webClient
    .get()
    .uri(uri)
    .attributes(oauth2AuthorizedClient(authorizedClient))
    // ...
    .retrieve()
    .bodyToMono(String.class);

经调试,代码已成功从数据库加载OAuth身份并将其作为属性添加到WebClient,但当WebClient执行retrieve时,出现错误IllegalArgumentException: serverWebExchange cannot be null。Stack Overflow上相关问题表明该错误发生在混合Servlet和响应式调用时,但我的Maven依赖只有WebFlux,因此排除该原因。

相关代码如下:

产品服务代码

public class ProductService {
    private final ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    private final ReactiveClientRegistrationRepository clientRegistrations;
    private static final String baseUri = "https://myapp.net/product";

    public ProductService(ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService,
            ReactiveClientRegistrationRepository clientRegistrations) {
        this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
        this.clientRegistrations = clientRegistrations;
    }

    public Mono<String> getNotifications(String productName, String userName) {
        String dataUri = "/{id}/notifications";
        Mono<OAuth2AuthorizedClient> userOauth = oAuth2AuthorizedClientService.loadAuthorizedClient("xxx", userName);
        Mono<Long> productId = this.lookupProductId(productName);

        return Mono.zip(productId, userOauth).checkpoint().flatMap(tuple2 ->
                this.getUserWebClient().get()
                        .uri(uriBuilder ->
                                uriBuilder.path(dataUri)
                                        .queryParam("datasource", "development")
                                        .build(tuple2.getT1().toString()))
                        .attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(tuple2.getT2()))
                        .retrieve()
                        .bodyToMono(String.class));
    }

    private WebClient getUserWebClient() {
        var authorizedClients = new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(oAuth2AuthorizedClientService);
        var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
                clientRegistrations, authorizedClients);
        return WebClient.builder()
                .baseUrl(baseUri)
                .filter(oauth)
                .build();
    }

    public Mono<Long> lookupProductId(String name) {
        // business logic to lookup product based on name
    }
}

Web安全配置(替换默认内存Bean)

@Bean
public ReactiveOAuth2AuthorizedClientService dbOauth2AuthorizedClientService(DatabaseClient databaseClient,
        ReactiveClientRegistrationRepository clientRegistrationRepository) {
    return new R2dbcReactiveOAuth2AuthorizedClientService(databaseClient, clientRegistrationRepository);
}

解决建议

  • 替换AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository:这个Repository依赖Web请求上下文(即ServerWebExchange),如果你的服务在非Web场景(如内部服务调用、定时任务)执行,就会抛出serverWebExchange cannot be null错误。改用不依赖Web上下文的AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager:
    private WebClient getUserWebClient() {
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
                        clientRegistrations, oAuth2AuthorizedClientService);
        var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
                .baseUrl(baseUri)
                .filter(oauth)
                .build();
    }
    
  • 添加令牌自动刷新逻辑:如果加载的OAuth2AuthorizedClient访问令牌可能过期,给授权客户端管理器添加令牌刷新器,确保请求时使用有效令牌:
    authorizedClientManager.setAuthorizedClientProvider(
            ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                    .refreshToken()
                    .clientCredentials()
                    .build());
    
  • 验证调用上下文:如果ProductService的方法确实是从Web端点触发,可尝试将ServerWebExchange传入并设置到WebClient属性中,但非Web场景必须使用独立于Web上下文的授权客户端管理器。

内容的提问来源于stack exchange,提问作者Timothy Vogel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 17:35:04