I am using Tomcat7, Spring framework for ReST web services.
I am trying to call an https web service using Spring RestTemplate.
I am getting the following error:
unable to find valid certification path to requested target; nested
exception is javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target
I check online at stackoverflow. I tried the sample code from the url:
Access Https Rest Service using Spring RestTemplate
I couldn't get it to work.
Can anybody please tell me based on the code below what do I need to change?
Also can anybody tell me or provide me with the pom.xml file which java libraries would I need?
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.journaldev.spring.controller.EmpRestURIConstants;
import com.journaldev.spring.model.CostControlPost;
import com.journaldev.spring.model.Employee;
import com.journaldev.spring.model.RfxForUpdate;
import static org.junit.Assert.*;
import org.apache.commons.codec.binary.Base64;
import javax.net.ssl.*;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class TestExample2
{
public static final String SERVER_LIST="https://abc/sourcing/testServices";
#Test
public void testGetListOfServiceNames()
{
try
{
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(SERVER_LIST,HttpMethod.GET,null,String.class);
assertNotNull(response);
}
catch(Exception e)
{
System.out.println("e:"+e.getMessage());
}
}
}
Either you need to have certificates in your keystore or you can accept all certificates (kind off ignore certificate validation)
So you can re-define bean of rest template as
import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import java.security.cert.X509Certificate;
#Bean
public RestTemplate restTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
You should not need additional jars except for apache core, client and dependencies.
Related
I am using NTLM authentication for my service. How to create the NTLM authentication in my API service call can anyone help with that? I need the complete coding for NTLM authentication
We use the following code to work with NTLM in production. As you can see it checks whether configuration is correct by sending simple HTTP GET.
package xxx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.*;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.impl.auth.NTLMSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.transport.WebServiceMessageSender;
import org.springframework.ws.transport.http.HttpComponentsMessageSender;
import java.util.Arrays;
#Configuration
public class Configuration {
#Bean
public WebServiceMessageSender messageSender(
#Autowired final Credentials credentials,
#Autowired final HttpUriRequest handshake,
#Value("${service.timeout}") final int timeout
) {
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender();
CredentialsProvider credentialsProvider;
Registry<AuthSchemeProvider> registry;
RequestConfig requestConfig;
credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, credentials);
registry = RegistryBuilder.<AuthSchemeProvider> create()
.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.build();
HttpRequestInterceptor interceptor =
(request, context) -> request.removeHeaders(HttpHeaders.CONTENT_LENGTH);
requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setDefaultAuthSchemeRegistry(registry)
.setDefaultCredentialsProvider(credentialsProvider)
.addInterceptorFirst(interceptor)
.build();
try {
CloseableHttpResponse r = httpClient.execute(handshake);
if (log.isInfoEnabled()) {
log.info("Handshake initiated, response headers: {}",
Arrays.toString(r.getAllHeaders())
);
}
} catch (Exception e) {
log.error("Could not execute HTTP handshake request (method = {})",
handshake.getMethod(), e
);
}
messageSender.setHttpClient(httpClient);
return messageSender;
}
#Bean
public Credentials credentials(
#Value("${service.auth.username}") String user,
#Value("${service.auth.password}") String pass,
#Value("${service.auth.workstation}") String workstation,
#Value("${service.auth.domain}") String domain
) {
return new org.apache.http.auth.NTCredentials(user, pass, workstation, domain);
}
#Bean
public HttpUriRequest handshake(#Value("${service.uri}") final String uri) {
return new HttpGet(uri);
}
}
application.properties looks like this:
service.uri=http://somehost/somepath/SomeService.svc
service.action=http://somehost1/somepath1
service.timeout=3000
service.auth.username=someuser
service.auth.password=somepassword
service.auth.domain=somedomain
service.auth.workstation=anything
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);
}
This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 4 years ago.
I am new to Spring. I develop Service that Consuming RESTful service with certficate using Java
Here is my Config class:
package configuration;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.ResourceUtils;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.util.function.Supplier;
#Configuration
public class RestClientCertConfig {
private char[] allPassword = "allpassword".toCharArray();
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"), allPassword, allPassword)
.loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), allPassword)
.build();
HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
return builder
.requestFactory((Supplier<ClientHttpRequestFactory>)new HttpComponentsClientHttpRequestFactory(client))
.build();
}
}
And here is the class where I consume Restful EndPoint:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.net.URISyntaxException;
import java.util.Collections;
public class ECSConfigGet {
private static final String ECS_API_URI = "<RestEndPointToConsume";
#Autowired
private static RestTemplate restTemplate;
public static void main(String[] args) {
try {
makeECSCall("myTestHeaderValue");
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
private static void makeECSCall(String entityCode) throws RestClientException, URISyntaxException {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("entityCode", entityCode);
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
ResponseEntity responseEntity = restTemplate.exchange(ECS_API_URI, HttpMethod.GET, entity, String.class);
}
}
Did I completely misunderstood the concept? I would expect restTemplate would not be null with all the Annotations I use. Thank for any help!
NullPointerException is fixed. ECSConfigGet looks like:
package main;
import configuration.RestClientCertConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import services.modelsdto.ExpenseConfigDTO;
import java.util.Collections;
#SpringBootApplication
#Component
public class ECSConfigGet implements CommandLineRunner{
//API to call
private static final String ECS_API_URI = "<API_TO_CONSUME>";
#Autowired
private RestTemplate restTemplate;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RestClientCertConfig.class);
applicationContext.getBean(RestTemplate.class);
SpringApplication.run(ECSConfigGet.class, args);
}
private void makeECSCall(String entityCode) throws RestClientException {
ExpenseConfigDTO expenseConfigDTO = new ExpenseConfigDTO();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("entityCode", entityCode);
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
ResponseEntity responseEntity = restTemplate.exchange(ECS_API_URI, HttpMethod.GET, entity, String.class);
}
#Override
public void run(String... args) {
for (int i = 0; i < args.length; ++i) {
makeECSCall("myTestHeaderValue");
}
}
}
You're missing a bit of Spring boilerplate that you need to make #Autowired work. If you're using Spring Boot, you're close, but #Patrick is right generally: ECSConfigGet needs to be a bean by annotating it correctly, but you also need to run your application within an application context in order for any of the Spring magic to happen. I suggest checking out this tutorial on how to use Spring Boot in a command line application.
The high level is ECSConfigGet needs to be annotated with #SpringBootApplication and then have it implement CommandLineRunner and then from the run method, you will have access to the #Autowired component. Spring will instantiate ECSConfigGet and populate the properties. Also as #Roddy pointed out, RestTemplate cannot be static, either.
The ECSConfigGet class is not a bean so it can not autowire a component.
Add #Component as class annotation to ECSConfigGet
I am developing a Spring Boot Application which calls a REST-API which frequently performs a 303 See Other redirect to the proper location.
For a given resource I start with a random initial URL, intercept the redirect to store the proper location for the next request and at last perform the redirect.
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
#Configuration
class RestTemplateFactory {
private static final Logger LOG = LoggerFactory.getLogger(RestTemplateFactory.class);
#Autowired
KeyMap keyMap;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
HttpClient httpClient = HttpClientBuilder
.create()
.setRedirectStrategy(new DefaultRedirectStrategy() {
#Override
public boolean isRedirected(HttpRequest request, HttpResponse response,
HttpContext context) throws ProtocolException {
if (super.isRedirected(request, response, context)) {
String redirectURL = response.getFirstHeader("Location").getValue();
LOG.debug("Intercepted redirect: original={}, redirect={}", request.getRequestLine(),
redirectURL);
keyMap.put(redirectURL);
return true;
}
return false;
}
})
.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return builder
.requestFactory(requestFactory)
.build();
}
}
(The class KeyMap is used to store the location for some domain key, which is stored in a ThreadLocal before the RestTemplate is invoked.)
Question: How can I test this special RestTemplate?
I am trying to set timeout for WS call. I extended WebServiceGatewaySupport and was trying to set to Sender timeout like this:
public Object marshalSendAndReceive(Object requestPayload) {
WebServiceTemplate wsTemplate = this.getWebServiceTemplate();
for (WebServiceMessageSender sender : wsTemplate.getMessageSenders()) {
try {
HttpComponentsMessageSender httpSender = (HttpComponentsMessageSender) sender;
httpSender.setReadTimeout(3000);
httpSender.setConnectionTimeout(2000);
} catch (ClassCastException | NumberFormatException cex) {
logger.warn("Cannot set WS timeout: " + cex.getMessage());
}
}
return wsTemplate.marshalSendAndReceive(requestPayload);
}
(credit to question #6733744)
However I get: Cannot set WS timeout: org.springframework.ws.transport.http.HttpsUrlConnectionMessageSender cannot be cast to org.springframework.ws.transport.http.HttpComponentsMessageSender
Can timeout be set to HttpsUrlConnectionMessageSender somehow? Or is there any other way to set timeout to https ws call in spring-boot?
Thank you.
I had the same issue, and managed to make it work using HttpComponentsMessageSender. Here is my code:
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender();
HttpClient httpClient = HttpClientFactory.getHttpsClient(sslUtils, timeout);
messageSender.setHttpClient(httpClient);
webServiceTemplate.setMessageSender(messageSender);
I also created a new factory class HttpClientFactory that sets the SSL and timeout:
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
public class HttpClientFactory {
private static CloseableHttpClient client;
private HttpClientFactory() {
}
public static HttpClient getHttpsClient(SslUtils sslUtils, int timeout) throws Exception {
if (client != null) {
return client;
}
SSLContext sslcontext = getSSLContext(sslUtils);
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.addInterceptorFirst(new ContentLengthHeaderRemover());
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
return httpClientBuilder.setSSLSocketFactory(factory)
.setDefaultRequestConfig(config)
.build();
}
private static class ContentLengthHeaderRemover implements HttpRequestInterceptor {
#Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
request.removeHeaders(HTTP.CONTENT_LEN);
}
}
public static void releaseInstance() {
client = null;
}
private static SSLContext getSSLContext(SslUtils sslUtils) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(sslUtils.getKeystore().getInputStream(), sslUtils.getKeyPwd().toCharArray());
sslUtils.getKeystore().getInputStream().close();
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(sslUtils.getTrustStore().getInputStream(), sslUtils.getTrustPwd().toCharArray());
sslUtils.getTrustStore().getInputStream().close();
SSLContextBuilder sslContextBuilder = SSLContexts.custom();
try {
sslContextBuilder = SSLContexts.custom().loadKeyMaterial(ks, ssl.getKeyPwd().toCharArray());
} catch (UnrecoverableKeyException e) {
e.printStack();
}
sslContextBuilder.loadTrustMaterial(ts, new TrustSelfSignedStrategy());
return sslContextBuilder.build();
}
}
For information the SslUtils is just a bean class that holds the keystore and truststore informations' :
public class SslUtils {
private Resource keystore;
private String keyPwd;
private Resource trustStore;
private String trustPwd;
// Getters and Setters
}
This works for me and let me use both SSL and timeout at the same. I hope this will help others.
You're getting a cast exception because HttpsUrlConnectionMessageSender and HttpComponentsMessageSender are not compatible types. They both share the same superclass (WebServiceMessageSender), but you can't cast one to the other. I think you can just use a HttpComponentsMessageSender in your WebServiceTemplate configuration. See How to set timeout in Spring WebServiceTemplate and Spring webServiceTemplate connection timeout property