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
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);
}
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 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.
i want to create an embedded jetty server (not maven, gradle etc.) to test a spring rest service.
Therefor i created an EmbeddedServer class. But unfortunately invocation of the rest service leads always to an http 404 error. What doing wrong?
The rest service works fine with tomcat (not embedded).
Im using the following dependencies:
"org.eclipse.jetty:jetty-server:8.1.17.v20150415"
"org.eclipse.jetty:jetty-webapp:8.1.17.v20150415"
Here is the EmbededServer class:
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class EmbeddedServer {
private Server server;
public void start() throws Exception {
this.server = createServer();
this.server.start();
}
public void stop() throws Exception {
this.server.stop();
this.server.join();
this.server.destroy();
}
private Server createServer() {
final Server server = new Server(8080);
final WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setResourceBase("src/main/webapp");
context.setDescriptor("src/main/webapp/WEB-INF/web.xml");
context.setParentLoaderPriority(true);
context.setServer(server);
server.setHandler(context);
return server;
}
}
And the test class:
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
public class SimpleTest {
private HttpClient client;
private EmbeddedServer server;
#Before
public void before() throws Exception {
this.client = new DefaultHttpClient();
this.server = new EmbeddedServer();
this.server.start();
}
#After
public void after() throws Exception {
this.server.stop();
}
#Test
public void invokeHello() throws Exception {
final HttpResponse response = invokeGetRequest("http://localhost:8080/hello");
verifyResponse(response, 200, "hello");
}
private HttpResponse invokeGetRequest(final String path) throws Exception {
final URI wsAddress = new URI(path);
final HttpGet method = new HttpGet(wsAddress);
return this.client.execute(method);
}
private void verifyResponse(final HttpResponse actualHttpResponse, final int expectedHttpCode, final String expectedResponse) throws IOException {
assertThat(actualHttpResponse.getStatusLine().getStatusCode(), equalTo(expectedHttpCode));
final String actualResponse = EntityUtils.toString(actualHttpResponse.getEntity(), "UTF-8");
assertThat(actualResponse, equalTo(expectedResponse));
}
}
This test fails with:
java.lang.AssertionError:
Expected: <200>
but: was <404>
Path of ResourceBase and Descriptor must be absolute.
context.setResourceBase("c:/myapp/src/main/webapp");
context.setDescriptor("c:/myapp/src/main/webapp/WEB-INF/web.xml");