Proxy Support For Authorization Requests With Spring-Security-OAuth2-Client
Setting up a proxy for spring-security-oauth2-client authorization requests. Or, how to get rid of the OAuth2AuthorizationException with nested UnknownHostException.
In our project, one of our new interface partners is using OAuth2 to secure their REST API. We’re using spring-security-oauth2-client to access it. Initially, we had some trouble because the newest version of this library is using WebClient instead of RestTemplate but this isn’t part of this blog post. We developed our feature locally and everything worked as expected. The library works like a charm and manages the access token without us bothering about it. Unlike our development environment, the production environment does not have direct internet access. Since our partner’s interface is on the internet we need to use a proxy so that we can access it. Therefore we added proxy support to our service, deployed it, tested it and surprisingly got the following exception:
Caused by: org.springframework.security.oauth2.core.OAuth2AuthorizationException: [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://*****/oauth/token“: *****; nested exception is java.net.UnknownHostException: ***** |
The solution
It took us some time to figure out why this is happening. Before I describe the solution, we should recall how OAuth2 is working. First, it sends a request to an authorization server which will return an access token if the permissions are correct and everything is working. This access token will be used in every future request to the resource server as long as it’s valid.
As the exception – OAuth2AuthorizationException – implies something went wrong during the authorization request. After digging through the source code of spring-security-oauth2-client we found out that the authorization request is using a different client than the resource requests. This means that at that time the proxy was only configured for the resource requests. So we need to configure the proxy for the authorization request separately. Funny enough this request is still using RestTemplate. At least if you’re using the ClientCredentialsOAuth2AuthorizedClientProvider. Enough of text. This is our solution:
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( final ClientRegistrationRepository clientRegistrationRepository, final OAuth2AuthorizedClientService authorizedClientService) { // Create RestTemplate that will be used for the authorization request // It's mandatory to add FormHttpMessageConverter and OAuth2AccessTokenResponseHttpMessageConverter // See javadoc from DefaultClientCredentialsTokenResponseClient.setRestOperations(RestOperations restOperations) // for further information RestTemplate restTemplate = new RestTemplate( Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); // set up proxy for RestTemplate final HttpClientBuilder clientBuilder = HttpClientBuilder.create(); clientBuilder.useSystemProperties(); clientBuilder.setProxy(new HttpHost(proxyConfigDO.getHost(), proxyConfigDO.getPort())); final CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials( new AuthScope(proxyConfigDO.getHost(), proxyConfigDO.getPort()), new UsernamePasswordCredentials(proxyConfigDO.getUser(), proxyConfigDO.getPassword())); clientBuilder.setDefaultCredentialsProvider(credsProvider); clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); final CloseableHttpClient client = clientBuilder.build(); final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setHttpClient(client); restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(factory)); // Create new client and pass our custom resttemplate final var tokenResponseClient = new DefaultClientCredentialsTokenResponseClient(); tokenResponseClient.setRestOperations(restTemplate); // Create ClientCredentialsOAuth2AuthorizedClientProvider and override default setAccessTokenResponseClient // with the one we created in this method final var authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(tokenResponseClient); final var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
This is just another example of how valuable open sources are. I hope this blogpost helped you solve your problem. Feel free to ask if you have any open questions.
Hi, sorry to hear that. Could you give us a bit more context?
I don’t know your project setup, but you might be missing a dependency.
We are having the same problem, but your Solution did not work. When calling
factory.setHttpClient(client);
we are getting an error
Cannot access org.apache.hc.client5.http.classic.HttpClient
Thanks a lot. Had the same issue. Your solution works fine. Hope Spring OAuth2 will move from RestTemplate to WebClient to avoid these issues.