I am new to Spring Boot. So far I am enjoying it. I have developed a demo SSL rest web server that correctly handles mutual X.509 certificate authentication. Using an IE browser with self signed client & server certificates, I have tested that the demo rest web server is working correctly -- both the server and browser are successfully exchanging and validating each others certificates.
I am having trouble finding an SSL client example that shows how to include the client certificate and issue the https. Anybody have a simple rest client example that shows how to consume my ssl server?
Best Regards,
Steve Mansfield
Given that you're using Spring, here's an example that shows how to use Spring's RestTemplate
and Apache's HttpClient
configured with a client certificate and to trust a self-signed certificate from the server:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File("keystore.jks")),
"secret".toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, "password".toCharArray()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
ResponseEntity<String> response = restTemplate.getForEntity(
"https://localhost:8443", String.class);
I could not get the above client submitted by Andy to work. I kept getting errors saying that "localhost != clientname". Anyways, I got this to work correctly.
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.GetMethod;
public class SSLClient {
static
{
System.setProperty("javax.net.ssl.trustStore","c:/apachekeys/client1.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.keyStore", "c:/apachekeys/client1.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
}
public static void main(String[] args) throws HttpException, IOException {
HttpClient client = new HttpClient();
GetMethod method = new GetMethod();
method.setURI(new URI("https://localhost:8443/restserver", false));
client.executeMethod(method);
System.out.println(method.getResponseBodyAsString());
}
}
The example of user1707141 didn´t work for me and skmansfield seems rather depending on specific files, that aren´t convention with Spring Boot / Maven. Also Andy Wilkinson´s answer uses the constructor SSLConnectionSocketFactory, which was deprecated in Apache httpclient 4.4+ and also seems quite complex.
So I created a example project that should show everything 100% comprehensible here: https://github.com/jonashackt/spring-boot-rest-clientcertificate
Besides the normal usage of the RestTemplate with @Autowired
in your Testclass, be sure to configure your RestTemplate like this:
package de.jonashackt.restexamples;
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.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.ResourceUtils;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
@Configuration
public class RestClientCertTestConfiguration {
private String allPassword = "allpassword";
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"), allPassword.toCharArray(), allPassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), allPassword.toCharArray())
.build();
HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
return builder
.requestFactory(new HttpComponentsClientHttpRequestFactory(client))
.build();
}
}
I know its too late, but here is the code that works for me.
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String args[]) {
makeWebServiceCall();
}
public static void makeWebServiceCall() {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext;
ResponseEntity<String> response = null;
try {
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);
StringBuffer plainCreds = new StringBuffer();
plainCreds.append("username");
plainCreds.append(":");
plainCreds.append("password");
byte[] plainCredsBytes = plainCreds.toString().getBytes();
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
String userBase64Credentials = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + userBase64Credentials);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity<>(headers);
String url = "https:restUrl";
response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
if(response.getStatusCodeValue() == 200) {
log.info("Success! Further processing based on the need");
} else {
log.info("****************Status code received: " + response.getStatusCodeValue() + ".************************");
}
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
log.error("Exception occured. Here are the exception details: ", e);
} catch(HttpClientErrorException e) {
if(e.getRawStatusCode() == 403) {
log.info("****************Status code received: " + e.getRawStatusCode() + ". You do not have access to the requested resource.************************");
} else if(e.getRawStatusCode() == 404) {
log.info("****************Status code received: " + e.getRawStatusCode() + ". Resource does not exist(or) the service is not up.************************");
} else if(e.getRawStatusCode() == 400) {
log.info("****************Status code received: " + e.getRawStatusCode() + ". Bad Request.************************");
} else {
log.info("****************Status code received: " + e.getRawStatusCode() + ".************************");
}
log.info("****************Response body: " + e.getResponseBodyAsString() + "************************");
}
}
}
Here is the maven filed
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-consuming-rest</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
This worked for me :
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
javax.net.ssl.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);