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

Spring Security+WebClient获取OAuth2令牌时连接超时问题排查

Spring Boot 3.0.X WebClient 代理配置导致OAuth2令牌请求超时问题排查

项目背景

基于Spring Boot 3.0.X的Java服务,正在从RestTemplate迁移至WebClient,调用外部系统时遭遇OAuth2令牌请求超时错误,怀疑是代理配置问题。此前存在自定义令牌请求参数(account_id)的需求。

核心配置信息

自定义OAuth2令牌请求要求

令牌请求URI:xxxxxxx/token
请求体格式:

{
    "grant_type": "client_credentials",
    "client_id": "xxxx",
    "client_secret": "xxxxxxxx",
    "account_id": "123456789"
}

application.yml 配置

security:
  oauth2:
    client:
      registration:
        custom-client:
          provider: custom-client
          client-id: xxxxxxxxxxxxxxx
          client-secret: xxxxxxxxxxxxxxx
          authorization-grant-type: client_credentials
          client-authentication-method: post
      provider:
        custom-client:
          token-uri: xxxxxxxxxxxxxxxxxxxxxxxxxx/token

初始WebClient配置(未添加代理)

@Configuration
@Getter
@Setter
public class WebClientConfig {

    @Bean
    public OAuth2AuthorizedClientManager webclientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                           OAuth2AuthorizedClientService authorizedClientService) {
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean
    public WebClient customWebClient(@Qualifier("webclientManager") OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oAuth2Filer = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oAuth2Filer.setDefaultClientRegistrationId("customclient");
        return WebClient.builder()
                .filter(oAuth2Filer)
                .baseUrl(apiUrl)
                .build();
    }

}

业务调用代码(MyService.java)

@Service
public class MyService{

    WebClient webClient;

    public MyService(@Qualifier("customClient") WebClient webClient) {
        this.webClient = webClient;
    }

    public String testRequest() {
        return webClient.get()
                .uri("xxxxxx")
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }
}

错误信息

"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for "https:/xxxxxxxxxxxxxx/oauth2/token": A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond; nested exception is java.net.SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond"

已尝试的代理配置(未解决)

@Configuration
@Getter
@Setter
public class WebClientConfig {

    @Bean
    public OAuth2AuthorizedClientManager webclientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                           OAuth2AuthorizedClientService authorizedClientService) {
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean
    public WebClient customWebClient(@Qualifier("webclientManager") OAuth2AuthorizedClientManager authorizedClientManager,
final @Value("${proxy.url}") String proxyUrl,
final @Value("${proxy.port}") String proxyPort,
final @Value("${proxy.username}") String proxyUsername,
final @Value("${proxy.password}") String proxyPassword) {

        ServletOAuth2AuthorizedClientExchangeFilterFunction oAuth2Filer = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oAuth2Filer.setDefaultClientRegistrationId("customclient");

        Function<String, String> httpsProxyPassword = username -> proxyPassword;

        HttpClient client = HttpClient.create()
            .proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP)
                .host(proxyUrl)
                .port(proxyPort)
                .password(httpsProxyPassword)
                .username(proxyUsername));

        return WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(client))
                .filter(oAuth2Filer)
                .baseUrl(apiUrl)
                .build();
    }

}

问题排查与解决方案

1. 核心问题:代理未作用于OAuth2令牌请求

当前配置的代理仅针对WebClient的业务请求,但OAuth2令牌请求是由AuthorizedClientServiceOAuth2AuthorizedClientManager默认使用RestTemplate发起的,不会复用WebClient的代理配置。需要为令牌请求单独配置代理:

方案1:配置带代理的RestTemplate用于令牌请求

@Bean
public OAuth2AuthorizedClientManager webclientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                     OAuth2AuthorizedClientService authorizedClientService,
                                                     @Value("${proxy.url}") String proxyUrl,
                                                     @Value("${proxy.port}") int proxyPort,
                                                     @Value("${proxy.username}") String proxyUsername,
                                                     @Value("${proxy.password}") String proxyPassword) {
    // 配置代理请求工厂
    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyUrl, proxyPort));
    requestFactory.setProxy(proxy);
    
    // 设置代理认证
    Authenticator authenticator = new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(proxyUsername, proxyPassword.toCharArray());
        }
    };
    Authenticator.setDefault(authenticator);
    
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    
    // 自定义令牌响应客户端,添加account_id参数
    ClientCredentialsTokenResponseClient tokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
    tokenResponseClient.setRestOperations(restTemplate);
    tokenResponseClient.setRequestEntityConverter(new OAuth2ClientCredentialsGrantRequestEntityConverter() {
        @Override
        protected MultiValueMap<String, String> createParameters(OAuth2ClientCredentialsGrantRequest grantRequest) {
            MultiValueMap<String, String> parameters = super.createParameters(grantRequest);
            parameters.add("account_id", "123456789");
            return parameters;
        }
    });

    OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials(c -> c.tokenResponseClient(tokenResponseClient))
            .build();

    AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
            new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
}

方案2:用WebClient发起令牌请求,统一代理配置

@Bean
public ClientCredentialsTokenResponseClient webClientTokenResponseClient(WebClient.Builder webClientBuilder,
                                                                       @Value("${proxy.url}") String proxyUrl,
                                                                       @Value("${proxy.port}") int proxyPort,
                                                                       @Value("${proxy.username}") String proxyUsername,
                                                                       @Value("${proxy.password}") String proxyPassword) {
    // 配置带代理的HttpClient
    HttpClient httpClient = HttpClient.create()
            .proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP)
                    .host(proxyUrl)
                    .port(proxyPort)
                    .username(proxyUsername)
                    .password(username -> proxyPassword));

    WebClient webClient = webClientBuilder
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();

    // 自定义令牌请求逻辑
    return request -> {
        ClientRegistration clientRegistration = request.getClientRegistration();
        return webClient.post()
                .uri(clientRegistration.getProviderDetails().getTokenUri())
                .headers(headers -> {
                    headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
                    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                })
                .body(BodyInserters.fromFormData("grant_type", "client_credentials")
                        .with("client_id", clientRegistration.getClientId())
                        .with("client_secret", clientRegistration.getClientSecret())
                        .with("account_id", "123456789"))
                .retrieve()
                .bodyToMono(OAuth2AccessTokenResponse.class)
                .map(response -> OAuth2AccessTokenResponse.withToken(response.getTokenValue())
                        .tokenType(response.getTokenType())
                        .expiresIn(response.getExpiresIn())
                        .build());
    };
}

// 在OAuth2AuthorizedClientManager中配置该客户端
@Bean
public OAuth2AuthorizedClientManager webclientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                     OAuth2AuthorizedClientService authorizedClientService,
                                                     ClientCredentialsTokenResponseClient tokenResponseClient) {
    OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials(c -> c.tokenResponseClient(tokenResponseClient))
            .build();

    AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
            new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
}

2. 基础验证步骤

  • 检查代理端口参数:当前代码用String接收proxy.port,需改为int类型,否则端口配置无效
  • 用curl验证代理连通性:
    curl -x http://${proxy.username}:${proxy.password}@${proxy.url}:${proxy.port} -X POST https://xxxxxxxxxxxxxx/oauth2/token -d "grant_type=client_credentials&client_id=xxxx&client_secret=xxxxxxxx&account_id=123456789"
    
  • 确认服务器网络权限:检查应用服务器是否能ping通代理、telnet代理端口,是否有防火墙/VPN限制出站请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:34:54