I am writing a server using Spring Boot v 1.2.7.RELEASE on Java 1.8. My code is configured entirely with annotations and currently has no XML other than the Maven pom.
My Spring Boot server has to query a WSDL server to authenticate users. In this context, my server is a client of the WSDL service. My app needs to present a certificate to the WS server to authenticate itself and gain access and then make the query. So, I have a JKS with my application cert and a server truststore JKS.
Also, the connection to the WS server is done via https, but I think that is handled by WebServiceGatewaySupport.
I have found many examples of a WS client, and many examples of configuring SSL in Spring Boot as a server, but none showing how to use SSL as a client. In my research I have seen pages that hint that this can be done with a few annotations but nothing concrete.
I'm thinking this must be possible, any help will be greatly appreciated, thanks!
Here's what I have, using Apache HttpClient 4.5.2:
import java.io.IOException;
import java.io.InputStream;
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 java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.pox.dom.DomPoxMessageFactory;
import org.springframework.ws.transport.WebServiceMessageSender;
import org.springframework.ws.transport.http.HttpComponentsMessageSender;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
@Configuration
class ApplicationIntegrationTestConfiguration {
@Value("${lcm.request.endpoint}")
private String endpointUri;
@Value("${lcm.request.keystorepath}")
private Resource keyStore;
@Value("${lcm.request.keystorepass}")
private char[] keyStorePass;
@Value("${lcm.request.keystoretype}")
private String keyStoreType;
@Value("${lcm.request.truststorepath}")
private Resource trustStore;
@Value("${lcm.request.truststorepass}")
private char[] trustStorePass;
@Value("${lcm.request.truststoretype}")
private String trustStoreType;
private static final String ACCEPT_HEADER_VALUE = "application/xml";
@Bean
public WebServiceMessageSender messageSender(
LayeredConnectionSocketFactory factory) throws Exception {
Header header = new BasicHeader(HttpHeaders.ACCEPT, ACCEPT_HEADER_VALUE);
List<Header> defaultHeaders = Arrays.asList(header);
CloseableHttpClient client = HttpClientBuilder.create()
.setSSLSocketFactory(factory)
.setDefaultHeaders(defaultHeaders)
.build();
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender(
client);
// needed if used as a standalone client
//messageSender.afterPropertiesSet();
return messageSender;
}
@Bean
public LayeredConnectionSocketFactory sslFactory() {
try {
final KeyStore keystore = KeyStore.getInstance(this.keyStoreType);
try (InputStream readStream = this.keyStore.getInputStream()) {
keystore.load(readStream, this.keyStorePass);
}
final KeyStore truststore = KeyStore.getInstance(this.trustStoreType);
try (InputStream readStream = this.trustStore.getInputStream()) {
truststore.load(readStream, this.trustStorePass);
}
SSLContext sslContext = SSLContexts
.custom()
.loadTrustMaterial(truststore, null)
.loadKeyMaterial(keystore, this.keyStorePass)
.build();
SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(sslContext,
new DefaultHostnameVerifier()
);
return sslConnectionFactory;
} catch (KeyManagementException | UnrecoverableKeyException |
NoSuchAlgorithmException | KeyStoreException
| CertificateException | IOException e) {
throw new IllegalArgumentException(String.format("Problem with keystore %s or truststore %s",
this.keyStore, this.trustStore), e);
}
}
@Bean
public PingClient pingClient(Jaxb2Marshaller marshaller,
WebServiceMessageFactory messageFactory, WebServiceMessageSender messageSender) {
PingClient client = new PingClient();
client.setDefaultUri(this.endpointUri + "/Ping/v1");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
client.setMessageFactory(messageFactory);
client.setMessageSender(messageSender);
return client;
}
// this bean is the key in selecting between SOAP and POX (plain old XML)
@Bean(name = MessageDispatcherServlet.DEFAULT_MESSAGE_FACTORY_BEAN_NAME)
public WebServiceMessageFactory messageFactory() {
return new DomPoxMessageFactory();
}
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("my.packages");
return marshaller;
}
}
I also have a sample project doing pretty much the same here, and same with addition of using basic auth credentials here. They don't have keystore or truststore in place (as it's kind of hard to get publishable version of those), but they should help out.
Note that you cannot use .jks for client cert, you'll have to convert to .jceks keystore format.