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;
}
}
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With