Describe the bug
I have to migrate an existing OAuth2RestTemplate based OAuth2 microservice to Spring Security / WebClient because OAuth2RestTemplate is deprecated. However, the new implementation is unable to access the OAuth2 server through a proxy server. I've read various articles on this topic, including https://github.com/spring-projects/spring-security/issues/8966, and tried to include the logic described in these articles in my implementation. Without success unfortunately.
When the proxy server is disabled, the OAuth2 server and downstream server are being invoked as expected.
To Reproduce
The code can be found here: https://github.com/easyRider651/proxy-server-test (please rename the mvn directory to .mvn). NoProxyServerTest shows that the OAuth2 server and downstream server can be accessed when no proxy server is configured. ProxyServerTest shows that the OAuth2 server cannot be accessed when a proxy server is configured. DirectProxyServerInvocationTest shows that the MockProxyServer is actually working. And DownstreamServerProxyServerInvocationTest shows that the downstream server can accessed through a proxy server. WebClient is configured in WebClientConfig. This is potentially the best configuration so far, but I've tried many different configurations to solve this proxy server issue (including the use of https.proxyPort, https.proxyHost, http.proxyPort http.proxyHost).
A similar test approached is being used to test the deprecated OAuth2RestTemplate based implementation. And all four tests pass against that implementation.
Expected behavior
The OAuth2 server can be accessed when a proxy server is configured.
Sample
https://github.com/easyRider651/proxy-server-test
Comment From: jgrandja
@easyRider651 You need to configure the underlying ClientHttpConnector used by WebClient with the appropriate Proxy settings. Take a look at this comment for further context.
FYI, questions are better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it).
Comment From: easyRider651
@jgrandja Thank you for your reply. I was not fully sure whether this was a bug report or a question, that's why I posted it here. I did read your https://github.com/spring-projects/spring-security/issues/8966#issuecomment-684917955 comment carefully before creating this issue. The provided implementation is very much based on that comment.
I do configure the underlying ClientHttpConnector used by WebClient with the appropriate Proxy settings (see WebClientConfig):
@Bean
public ClientHttpConnector clientHttpConnector() {
log.debug("The proxy for the calls to the REST server is {}", restServiceProperties.getProxyUrl());
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
if (Strings.isNotEmpty(restServiceProperties.getProxyUrl())) {
try {
HttpHost proxy = HttpHost.create(restServiceProperties.getProxyUrl());
clientBuilder.setRoutePlanner(new DefaultProxyRoutePlanner(proxy));
} catch (URISyntaxException e) {
log.error("Error: {}", e.getMessage());
throw new RuntimeException(e);
}
}
CloseableHttpAsyncClient client = clientBuilder.build();
return new HttpComponentsClientHttpConnector(client);
}
I also tried clientBuilder.setProxy(proxy) rather than clientBuilder.setRoutePlanner(new DefaultProxyRoutePlanner(proxy)), but that didn't solve the issue.
@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(ReactiveClientRegistrationRepository clientRegistrationRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService, ClientHttpConnector clientHttpConnector, WebClient.Builder webClientBuilder) {
WebClient webClient = webClientBuilder
.clientConnector(clientHttpConnector)
.filter(logRequest())
.build();
..
@Bean
public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager,
ClientHttpConnector clientHttpConnector, WebClient.Builder webClientBuilder) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2FilterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2FilterFunction.setDefaultClientRegistrationId(REGISTRATION_ID);
return webClientBuilder
.clientConnector(clientHttpConnector)
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(oauth2FilterFunction);
exchangeFilterFunctions.add(logRequest());
})
.build();
}
Comment From: easyRider651
@jgrandja I did post a question on Stack Overflow: Spring WebClient is not using the proxy server with spring-security-oauth2-client-5.4.6 last week. As you proposed. No reply unfortunatly. The call to the downstream server and OAuth2 server have to go through a proxy server, so not using a proxy server is not an option. Do you have a suggestion how to proceed?
Comment From: jgrandja
@easyRider651 Have you confirmed that the underlying WebClientReactiveClientCredentialsTokenResponseClient is using the configured WebClient? If it is using the configured WebClient, then the issue is likely the Proxy configuration. Have you tried using the WebClient directly in a test to confirm the correct Proxy configuration?
Comment From: easyRider651
@jgrandja Yes, the underlying WebClientReactiveClientCredentialsTokenResponseClient is using the configured WebClient. This WebClient has been created using a ClientHttpConnectorwhich has been configured with the proxy settings. See my initial post and related link to my github repo. You can find the complete configuration here: WebClientConfig.java. Yes, I've tested the WebClient directly with a proxy server and it is working as expected.
Did you create a Spring Security OAuth2 WebClient proxy server integration test that shows how to implement it?
Comment From: jgrandja
@easyRider651 I ran the test DownstreamServerProxyServerInvocationTest.accessOauth2ServerAndDownstreamServerThroughProxyServer() and discovered that WebClientConfig is not configured correctly.
ReactiveOAuth2AuthorizedClientProviderBuilder.builder.clientCredentials() is configured with the proxied WebClient but password() and refreshToken() is NOT and the test uses a AuthorizationGrantType.PASSWORD ClientRegistration. You need to configure ReactiveOAuth2AuthorizedClientProviderBuilder.builder.password() as well.
Comment From: easyRider651
@jgrandja Thanks a lot for your investigation and reply!
This is the right configuration:
private ReactiveOAuth2AuthorizedClientProvider createAuthorizedClientProvider(WebClient webClient) {
WebClientReactiveClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
clientCredentialsTokenResponseClient.setWebClient(webClient);
WebClientReactivePasswordTokenResponseClient passwordTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();
passwordTokenResponseClient.setWebClient(webClient);
WebClientReactiveRefreshTokenTokenResponseClient refreshTokenTokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient();
refreshTokenTokenResponseClient.setWebClient(webClient);
return ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(builder -> builder.accessTokenResponseClient(clientCredentialsTokenResponseClient))
.password(builder -> builder.accessTokenResponseClient(passwordTokenResponseClient))
.refreshToken(builder -> builder.accessTokenResponseClient(refreshTokenTokenResponseClient))
.build();
}
I'll update stackoverflow as well, with a link to this ticket.
However, I noticed that after the migration of OAuth2RestTemplate to Spring Security / WebClient the number of calls to the OAuth2 server increased significantly. Could it be possible that Spring Security interprets the value of expires_in, in access and refresh tokens, as milliseconds rather than seconds (as specified in RFC 6749)?
Comment From: cervenf
could you please post a full code solution?