如何根据Salesforce OAuth2返回的instance_url动态设置WebClient的baseUrl?
基于Salesforce认证返回的instance_url动态修改WebClient baseUrl的实现方案
下面是几个可行的实现方案,帮你解决这个问题:
方案1:通过Filter动态替换请求BaseUrl(推荐)
这个方案不需要修改WebClient的初始化逻辑,而是在每次请求时,从已授权的客户端中提取instance_url并替换请求的URI。
步骤1:确认授权客户端已包含instance_url
Spring Security的默认实现会把Salesforce返回的所有额外参数(包括instance_url)存入OAuth2AuthorizedClient的accessToken.additionalParameters中,所以不需要额外自定义token客户端。
步骤2:修改WebClient配置,添加动态替换Filter
直接在WebClient构建时添加一个Filter,从请求属性中拿到已授权的客户端,提取instance_url后重写请求URI:
@Bean public WebClient salesforceWebClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Client.setDefaultClientRegistrationId(SALESFORCE); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .filter((request, next) -> { // 从请求属性中获取已授权的Salesforce客户端 return request.attribute(ServletOAuth2AuthorizedClientExchangeFilterFunction.AUTHORIZED_CLIENT_ATTR_NAME) .map(OAuth2AuthorizedClient.class::cast) .flatMap(authorizedClient -> { // 从token的额外参数中取出instance_url String instanceUrl = authorizedClient.getAccessToken().getAdditionalParameters() .get("instance_url").toString(); // 拼接新的请求URI:instance_url + 原请求路径 URI newUri = URI.create(instanceUrl + request.url().getPath()); ClientRequest newRequest = ClientRequest.from(request) .uri(newUri) .build(); return next.exchange(newRequest); }) .orElse(next.exchange(request)); }) .build(); }
方案2:自定义Token客户端缓存instance_url,按需使用
如果需要在其他地方也用到instance_url,可以把它缓存到一个单例Holder中,再在WebClient里使用:
步骤1:创建instance_url缓存Holder
@Component public class SalesforceInstanceUrlHolder { private String instanceUrl; public String getInstanceUrl() { return instanceUrl; } public void setInstanceUrl(String instanceUrl) { this.instanceUrl = instanceUrl; } }
步骤2:自定义密码模式的Token响应客户端
@Component public class SalesforcePasswordAccessTokenClient extends DefaultPasswordOAuth2AccessTokenResponseClient { private final SalesforceInstanceUrlHolder instanceUrlHolder; public SalesforcePasswordAccessTokenClient(SalesforceInstanceUrlHolder instanceUrlHolder) { this.instanceUrlHolder = instanceUrlHolder; } @Override public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest grantRequest) { OAuth2AccessTokenResponse tokenResponse = super.getTokenResponse(grantRequest); // 提取并缓存instance_url String instanceUrl = tokenResponse.getAdditionalParameters().get("instance_url").toString(); instanceUrlHolder.setInstanceUrl(instanceUrl); return tokenResponse; } }
步骤3:修改授权客户端管理器,使用自定义Token客户端
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService, SalesforcePasswordAccessTokenClient accessTokenClient ) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .password(configurer -> configurer.accessTokenResponseClient(accessTokenClient)) .build(); AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); authorizedClientManager.setContextAttributesMapper(oAuth2AuthorizeRequest -> { if (SALESFORCE.equals(oAuth2AuthorizeRequest.getClientRegistrationId())) { return Map.of( OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, SALESFORCE_USERNAME, OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, SALESFORCE_PASSWORD ); } return null; }); return authorizedClientManager; }
步骤4:WebClient中使用缓存的instance_url
@Bean public WebClient salesforceWebClient(OAuth2AuthorizedClientManager authorizedClientManager, SalesforceInstanceUrlHolder instanceUrlHolder) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Client.setDefaultClientRegistrationId(SALESFORCE); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .filter((request, next) -> { String instanceUrl = instanceUrlHolder.getInstanceUrl(); if (instanceUrl != null) { URI newUri = URI.create(instanceUrl + request.url().getPath()); ClientRequest newRequest = ClientRequest.from(request) .uri(newUri) .build(); return next.exchange(newRequest); } return next.exchange(request); }) .build(); }
方案3:认证成功后重新构建WebClient(单租户场景适用)
如果你的应用只对接一个Salesforce实例,且instance_url不会频繁变化,可以在首次认证成功后,重新构建带有正确baseUrl的WebClient:
步骤1:创建WebClient提供者Bean
@Component public class SalesforceWebClientProvider { private WebClient webClient; public WebClient getWebClient() { return webClient; } public void setWebClient(WebClient webClient) { this.webClient = webClient; } }
步骤2:初始化不带baseUrl的WebClient
@Bean public WebClient initialSalesforceWebClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Client.setDefaultClientRegistrationId(SALESFORCE); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .build(); }
步骤3:监听认证成功事件,更新WebClient
@Component public class SalesforceAuthSuccessListener implements ApplicationListener<OAuth2AuthorizationSuccessEvent> { private final WebClient initialWebClient; private final SalesforceWebClientProvider webClientProvider; public SalesforceAuthSuccessListener(WebClient initialSalesforceWebClient, SalesforceWebClientProvider webClientProvider) { this.initialWebClient = initialSalesforceWebClient; this.webClientProvider = webClientProvider; } @Override public void onApplicationEvent(OAuth2AuthorizationSuccessEvent event) { if (SALESFORCE.equals(event.getAuthorization().getRegisteredClientId())) { // 从授权信息中提取instance_url String instanceUrl = event.getAuthorization().getAccessToken().getAdditionalParameters() .get("instance_url").toString(); // 基于初始WebClient修改baseUrl WebClient newWebClient = initialWebClient.mutate() .baseUrl(instanceUrl) .build(); webClientProvider.setWebClient(newWebClient); } } }
之后业务代码注入SalesforceWebClientProvider,调用getWebClient()获取配置好的实例即可。
内容的提问来源于stack exchange,提问作者David LAUMON
相关产品推荐
相关产品推荐

