31

i want to consume a REST service with my spring application. To access that service i have a client certificate (self signed and in .jks format) for authorization. What is the proper way to authenticate against the rest service?

This is my request:

public List<Info> getInfo() throws RestClientException, URISyntaxException {

    HttpEntity<?> httpEntity = new HttpEntity<>(null, new HttpHeaders());

    ResponseEntity<Info[]> resp = restOperations.exchange(
            new URI(BASE_URL + "/Info"), HttpMethod.GET, 
            httpEntity, Info[].class);
    return Arrays.asList(resp.getBody());
}
Nas3nmann
  • 480
  • 1
  • 6
  • 13

2 Answers2

49

Here is example how to do this using RestTemplate and Apache HttpClient

You should define your own RestTemplate with configured SSL context:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
    char[] password = "password".toCharArray();

    SSLContext sslContext = SSLContextBuilder.create()
            .loadKeyMaterial(keyStore("classpath:cert.jks", password), password)
            .loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();

    HttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
    return builder
            .requestFactory(new HttpComponentsClientHttpRequestFactory(client))
            .build();
}

 private KeyStore keyStore(String file, char[] password) throws Exception {
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    File key = ResourceUtils.getFile(file);
    try (InputStream in = new FileInputStream(key)) {
        keyStore.load(in, password);
    }
    return keyStore;
}

Now all remote calls performed by this template will be signed with cert.jks. Note: You would need to put cert.jks into your classpath

@Autowired
private RestTemplate restTemplate;

public List<Info> getInfo() throws RestClientException, URISyntaxException {
    HttpEntity<?> httpEntity = new HttpEntity<>(null, new HttpHeaders());

    ResponseEntity<Info[]> resp = restTemplate.exchange(
            new URI(BASE_URL + "/Info"), HttpMethod.GET, 
            httpEntity, Info[].class);
    return Arrays.asList(resp.getBody());
}
Ruslan Poshuk
  • 518
  • 5
  • 7
  • 8
    Note that you can call `setRequestFactory` on a `RestTemplate` object directly, instead of having to use the `RestTemplateBuilder` – ChocolateAndCheese Jun 04 '18 at 18:36
  • I don't have `.jks` file. I have downloaded client certificate from GoDaddy and there are two files with `.crt` and `.pem`. I tried this code with these files but it throws exception `java.io.IOException: Invalid keystore format` Please guide. Thanks – Ankur Raiyani Apr 22 '20 at 11:13
  • convert .pem to .jks https://stackoverflow.com/questions/22296312/convert-certificate-from-pem-into-jks/22298627 – Glare Storm Sep 08 '20 at 09:10
  • Above code wont work for me. I generated pfx/pkcs12 from a bunch of pem files from https://www.sslshopper.com/ssl-converter.html, and converted pkcs12 to jks. – user1354825 Dec 06 '21 at 10:01
1

Or you can just import the certificate to your JDKs cacerts, and all the HTTP clients using the jdk (rest template in your case) will use the certificate to make the REST call.

keytool -import -keystore $JAVA_HOME/jre/lib/security/cacerts -file foo.cer -alias alias

P.S: Don't forget to restart your server after successful import. Default password for keystore - changeit

Abhijeet Ahuja
  • 5,596
  • 5
  • 42
  • 50
  • 9
    Does this cause issue if I have several different certs I want to use for different calls? – Janac Meena Jan 15 '19 at 14:57
  • 1
    How Java knows which client cert to use, when there are multiple certs in the cacerts store? – DAN Apr 06 '21 at 07:26
  • 5
    Client certificates are not specified in the cacerts file. This is for trusted certificates like the signing certificate for the server certificate being called. This answer is wrong. – Chris D Apr 09 '21 at 22:45