Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using RestHighLevelClient with OAuth2

I maintain a productive Spring Boot Service (2.3.x), that connects to a remote Elastic Search cluster using the RestHighLevelClient from elasticsearch-rest-high-level-client (7.10.x) for searching. This works like a charm, but now the remote service provider has secured its services with an OAuth2 proxy. This is ok for common REST endpoints, Spring provides everything you need: You just have to replace the standard Spring RestTemplate with the OAuth2RestTemplate from spring-security-oauth2. It also works fine with Kibana and a browser.

But the Elastic client is a hard nut to crack: I was not able to replace Elastic's RestClient with something that supports OAuth 2.0. Their code is really resistant against exchanging implementations. No interfaces, no layers of abstraction. Nevertheless I would like to keep Elastic's HighLevelClient, which is much more convenient than sending plain JSONs.

Has anybody successfully combined Elastic RestHighLevelClient with OAuth? Is there any compatible or alternative library?

Here's the Spring config I currently use:

import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;

import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;

@Configuration
public class ElasticSearchClientConfiguration {
    
    // Elastic HighLevelClient currently used for searching, esp. msearch
    @Bean
    RestHighLevelClient restHighLevelClient() throws KeyManagementException, SSLException, NoSuchAlgorithmException {
        return new RestHighLevelClient(createRestClientBuilder());
    }

    private RestClientBuilder createRestClientBuilder()
            throws KeyManagementException, SSLException, NoSuchAlgorithmException {

        // ... some more configs here

        return RestClient.builder(new HttpHost(host, port, scheme)).setHttpClientConfigCallback(callback);
    }
    
    // Spring's OAuth2RestTemplate, used to call some other remote REST endpoints.
    @Bean
    public OAuth2RestTemplate oauth2RestTemplate() {
        
        final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
        resourceDetails.setClientId(clientId);
        resourceDetails.setClientSecret(loadSecret(clientSecretFileName));
        resourceDetails.setScope(Collections.singletonList(resource));
        resourceDetails.setAccessTokenUri(accessTokenUri);

        final ClientCredentialsAccessTokenProvider tokenProvider = new ClientCredentialsAccessTokenProvider();
        
        final OAuth2RestTemplate template = new OAuth2RestTemplate(resourceDetails);
        template.setAccessTokenProvider(tokenProvider);
        
        return template;
    }
    
}   
like image 703
oliver_t Avatar asked May 24 '26 06:05

oliver_t


1 Answers

I finally found a solution that works. An Apache HttpRequestInterceptor can be used to append additional http headers.

import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OAuthInterceptor implements HttpRequestInterceptor {

    private static final String HEADER_AUTHORIZATION_KEY = "Authorization";

    @Autowired
    private OAuthTokenProvider tokenProvider; // this service fetches the token

    @Override
    public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
        if (!(request instanceof HttpRequestWrapper)) {
            throw new HttpException("Unsupported request type: " + request.getClass());
        }

        final HttpRequestWrapper wrapper = (HttpRequestWrapper) request;
        wrapper.addHeader(createAuthorizationHeader());
    }

    private Header createAuthorizationHeader() {
        final String auth = tokenProvider.getAuthorization();
        return new BasicHeader(HEADER_AUTHORIZATION_KEY, auth);
    }

}

The interceptor can then be injected in the factory method:

    @Bean
    RestHighLevelClient restHighLevelClient(OAuthInterceptor oAuthInterceptor) throws KeyManagementException, SSLException, NoSuchAlgorithmException {

        final CredentialsProvider credentialsProvider = createCredentialsProvider();

        final RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setSocketTimeout(socketTimeout)
                .build();

        final HttpClientConfigCallback callback = new HttpClientConfigCallback() {
            @Override
            public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder asyncClientBuilder) {
                return asyncClientBuilder
                        .setDefaultCredentialsProvider(credentialsProvider)
                        .setDefaultRequestConfig(config)
                        .addInterceptorFirst(oAuthInterceptor); // That's the trick
            }
        };

        final RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)).setHttpClientConfigCallback(callback);

        return new RestHighLevelClient(builder);
    }
like image 180
oliver_t Avatar answered May 26 '26 20:05

oliver_t