1 Secure REST Client
djmil edited this page 2024-11-24 18:25:23 +01:00

Since CSDE expose its services via REST API, our ReactJs and SpringBoot#Backend server needs a way to call HTTPS endpoints.

Apache HttpClient 5.0

Although the java.net package provides basic functionality for accessing resources via HTTP, it doesn't provide the full flexibility or functionality needed by many applications. HttpClient seeks to fill this void by providing an efficient, up-to-date, and feature-rich package implementing the client side of the most recent HTTP standards and recommendations.

build.gradle

dependencies {
	implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
	//implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.2.1'
}

quickStart.java

try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
    ClassicHttpRequest httpGet = ClassicRequestBuilder
		    .get("http://httpbin.org/get")
            .build();
    // The underlying HTTP connection is still held by the response object
    // to allow the response content to be streamed directly from the network
    // socket.
    // In order to ensure correct deallocation of system resources
    // the user MUST call CloseableHttpResponse#close() from a finally clause.
    // Please note that if response content is not fully consumed the underlying
    // connection cannot be safely re-used and will be shut down and discarded
    // by the connection manager.
    httpclient.execute(httpGet, response -> {
        System.out.println(response.getCode() + " " + response.getReasonPhrase());
        final HttpEntity entity1 = response.getEntity();
        // do something useful with the response body
        // and ensure it is fully consumed
        EntityUtils.consume(entity1);
        return null;
    });

    ClassicHttpRequest httpPost = ClassicRequestBuilder
		    .post("http://httpbin.org/post")
            .setEntity(new UrlEncodedFormEntity(Arrays.asList(
                    new BasicNameValuePair("username", "vip"),
                    new BasicNameValuePair("password", "secret"))))
            .build();
    httpclient.execute(httpPost, response -> {
        System.out.println(response.getCode() + " " + response.getReasonPhrase());
        final HttpEntity entity2 = response.getEntity();
        // do something useful with the response body
        // and ensure it is fully consumed
        EntityUtils.consume(entity2);
        return null;
    });
}

Secure HTTPS endpoints

If we would to try to to GET some data from CSDE on https://localhost:8888/api/v1/virtualnode we would fail with unable to find valid certification path to requested target runtime exception. This is because we need to use SLL to implement HTPPS support.

This sample client is suitable only for testing purposes, since it does not perform any form of serve cert validation, and establishes encrypted connection with any server.

naiveSecureRestClient.java

final TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;

final SSLContext sslContext = SSLContexts.custom()
	.loadTrustMaterial(null, acceptingTrustStrategy)
	.build();

final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
	.register("https", sslsf)
	.register("http", new PlainConnectionSocketFactory())
	.build();

final BasicHttpClientConnectionManager connectionManager = 
	new BasicHttpClientConnectionManager(socketFactoryRegistry);

try (CloseableHttpClient httpclient = HttpClients
		.custom()
		.setConnectionManager(connectionManager)
		.build()) {
	// Your HTTP handeling
}

Certification Authority

CA usage, is a common strategy for clients to ensure servers validity. In this project, CordApp uses self signed certificate, which our client shall use as its CA.

  1. Obtain servers certificate with

    openssl s_client -connect localhost:8888 2>&1 </dev/null | sed -ne '/BEGIN CERT/,/END CERT/p'
    
  2. Add CA cert to TrustStore

    keytool -importcert -keystore truststore.p12 -storepass [password] -file [certificate_file]
    

    The command will create a JavaKeyStore in PKCS12 format.

  3. Place truststore.p12 among servers resources files: src/main/resources/keystore/truststore.p12

  4. Use trust store to configure SSLContext: src/main/resources/application.properties

    trust.store=classpath:keystore/truststore.p12
    trust.store.password=test123
    

    stc/test/java/djmil/cordacheckers/CordacheckersApplicationTests.java

    @SpringBootTest
    class CordacheckersApplicationTests {
    
    @Value("${trust.store}")
    private Resource trustStore;
    
    @Value("${trust.store.password}")
    private String trustStorePassword;
    
    @Test
    void givenAcceptOnlyCACertificates_whenHttpsUrlIsConsumed_thenOk() throws GeneralSecurityException, IOException {
        final SSLContext sslContext = SSLContexts
     	   .custom()
     	   .loadTrustMaterial(trustStore.getURL(), trustStorePassword.toCharArray())
     	   .build();
        final SSLConnectionSocketFactory sslsf = ...;
     }
    }
    

RestTemplate

At this point, we shall be able to fetch data from HTTPS endpoint. HttpClient is a general-purpose library to communicate using HTTP. If we want to have higher level of abstraction dealing with JSON/XML objects, we have to use RestTemplate - it can bind data to custom domain types.

Pojo

package djmil.cordacheckers.pojo;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record virtualnode(List<virtualNodes> virtualNodes) { }

This simple Java record class is annotated with @JsonIgnoreProperties from the Jackson JSON processing library to indicate that any properties not bound in this type should be ignored, and represent a top level POJO representation of JSON document returned from the CSDE API for https://localhost:8888/api/v1/virtualnode endpoint.

For direct binding of some data to some custom type, you need to specify the variable name to be exactly the same as the key in JSON. In case the variable name and the key do not match - use @JsonProperty annotation to specify the exact key of the JSON document.

Implementation example

Create Bean for RestTemplate under the @Configuration annotated class. You can even write a separate class and annotate with @Configuration

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
       return builder.build();
    }
}

Then, define RestTemplate with @Autowired or @Injected under your Service / Controller

@Autowired
private RestTemplate restTemplate;

All is ready to perform actual REST API request with help of RestTemplate. For this we can use multiple methods like exchange(), getForEntity(), getForObject().

// Request authorization header
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth("admin", "admin");

// String authStr = "username:password";
// String base64Creds = Base64.getEncoder().encodeToString(authStr.getBytes());
// headers.add("Authorization", "Basic " + base64Creds);

// Request
final HttpEntity<String> request = new HttpEntity<>(headers);

ResponseEntity<virtualnode> response = new RestTemplate(requestFactory)
		.exchange("https://localhost:8888/api/v1/virtualnode",
		          HttpMethod.GET,
		          request,
		          virtualnode.class );

if (response.getStatusCode() == HttpStatus.OK && 
	response.hasBody()) {
	virtualnode vNode = response.getBody();
}

Configure RestTemplate to use HttpClient

The final bit of a puzzle, is that we need a way to tell HttpClient to use RestTemplate

CloseableHttpClient httpClient = HttpClientBuilder
	//.create()
	//.disableCookieManagement()
	.setConnectionManager(connectionManager)
	.build();

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);

factory.setReadTimeout(readTimeout);
factory.setConnectTimeout(connectTimeout);

RestTemplate restTemplate = new RestTemplate(factory);