Secure REST Client
parent
7e41cd8c0f
commit
bdfc2716c7
@ -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:
|
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.
|
- 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.
|
- 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.
|
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.
|
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:
|
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.
|
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.
|
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.
|
[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
|
/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.
|
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:
|
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
|
```sh
|
||||||
./gradlew startCord
|
./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
|
> [!note] Be sure to have your Docker demon up and running
|
||||||
|
|
||||||
### Run virtual nodes
|
## Run virtual nodes
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./gradlew 5-vNodesSetup
|
./gradlew 5-vNodesSetup
|
12
Home.md
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
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);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user