如何将非默认用户的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
相关产品推荐
相关产品推荐

