Secure REST Client

djmil 2023-08-18 13:48:57 +02:00
parent 7e41cd8c0f
commit bdfc2716c7
3 changed files with 239 additions and 11 deletions

@ -1,5 +1,3 @@
# CSDE
The CorDapp Standard Development Environment (CSDE) makes the process of prototyping CorDapps more straight-forward. The CSDE is obtained by cloning our `CSDE-cordapp-template-kotlin` or `CSDE-cordapp-template-java` repository to your local machine. The CSDE provides:
- a prepared CorDapp project that you can use as a starting point to develop your own prototypes.
@ -11,13 +9,13 @@ The CorDapp Standard Development Environment (CSDE) makes the process of prototy
- the ability to configure the members of the local Corda network.
## Installation
# Installation
### Prerequisites
## Prerequisites
Be sure to [install](https://docs.r3.com/en/platform/corda/5.0/developing-applications/getting-started/prerequisites.html) required SW and CLI tools prior starting development with CSDE.
## Template project
# Template project
Template Java or Kotlin projects are availvable as a git repo.
@ -31,7 +29,7 @@ git remote add origin <remote-url>
In order for the `CSDE-cordapp-template` app to work on MacOS with VsCode, these steps must be made:
### Add `$rootDir` prefix in `build.gradle`
## Add `$rootDir` prefix in `build.gradle`
For some reason, to run gradle tasks from VsCode, the default `build.gradle` must be updated with absolute paths. Unpatched file works just fine for commands from CLI.
@ -61,7 +59,7 @@ For some reason, to run gradle tasks from VsCode, the default `build.gradle` mus
Note, that there must be no prefix for `r3RootCertFile` property.
### JDK
## JDK
[Install](https://docs.azul.com/core/zulu-openjdk/install/macos) recommend JDK (Azul Zulu 11) and tell gradle to use it.
@ -94,13 +92,13 @@ Even if CordaCLI is actually installed!
/usr/libexec/java_home -V
```
## Execution
# Execution
Gradle commands shall be accessible to you at this moment. Although, it is possible to run gradle tasks in VsCode, it is **suggested to execute them as a generic bash commands**, since most of the configuration files are tested and finetuned only for JetBrains IDEA.
Type `./gradlew tasks` to get list of availvable tasks. The most basic of which are:
### Start local Corda application network
## Start local Corda application network
```sh
./gradlew startCord
@ -108,7 +106,7 @@ Type `./gradlew tasks` to get list of availvable tasks. The most basic of which
> [!note] Be sure to have your Docker demon up and running
### Run virtual nodes
## Run virtual nodes
```sh
./gradlew 5-vNodesSetup

12
Home.md

@ -1 +1,11 @@
Welcome to the Wiki.
# Major development milestones
- [[ReactJs and SpringBoot]]
Classic WebApp with ReactJS frontend and SpringBoot backend.
- [[CSDE|Corda Standard Dev Environment]]
CSDE will be hosting and enforcing Checkers game logic.
- [[Secure REST Client]]
Communication channel between SpringBoot server and CordApp.
# TODOs
- Use [jsonschema2pojo](https://github.com/joelittlejohn/jsonschema2pojo/tree/master/jsonschema2pojo-gradle-plugin) to generate POJO objects for SpringBoot server from REST API schemas provided by CSDE

220
Secure REST Client.md Normal file

@ -0,0 +1,220 @@
Since [[CSDE]] expose its services via REST API, our [[ReactJs and SpringBoot#Backend|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](https://hc.apache.org/httpcomponents-client-5.2.x/quickstart.html) 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`*
```groovy
dependencies {
implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
//implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.2.1'
}
```
*`quickStart.java`*
```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`*
```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
```sh
openssl s_client -connect localhost:8888 2>&1 </dev/null | sed -ne '/BEGIN CERT/,/END CERT/p'
```
2. Add CA cert to TrustStore
```sh
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`*
```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](https://spring.io/guides/gs/consuming-rest/) - it can bind data to custom domain types.
## Pojo
```java
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
```java
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
```
Then, define **RestTemplate** with **@Autowired** or **@Injected** under your <abbr title="where ever you are trying to use RestTemplate">Service / Controller</abbr>
```java
@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()**.
```java
// 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
```java
CloseableHttpClient httpClient = HttpClientBuilder
//.create()
//.disableCookieManagement()
.setConnectionManager(connectionManager)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setReadTimeout(readTimeout);
factory.setConnectTimeout(connectTimeout);
RestTemplate restTemplate = new RestTemplate(factory);
```