1. Introduction

In all the examples to date (and likely forward), we have been using the HTTP protocol. This has been a very easy option to use, but I likely do not have to tell you that straight HTTP is NOT secure for use and especially NOT appropriate for use with credentials or any other authenticated information.

Hypertext Transfer Protocol Secure (HTTPS) — with trusted certificates — is the secure way to communicate using APIs in modern environments. We still want the option of simple HTTP in development, and most deployment environments provide an external HTTPS proxy that can take care of secure communications with the external clients. However, it will be good to take a short look at how we can enable HTTPS directly within our Spring Boot application.

1.1. Goals

You will learn:

  • the basis of how HTTPS forms trusted, private communications

  • the difference between self-signed certificates and those signed by a trusted authority

  • how to enable HTTPS/TLS within our Spring Boot application

1.2. Objectives

At the conclusion of this lecture and related exercises, you will be able to:

  1. define the purpose of a public certificate and private key

  2. generate a self-signed certificate for demonstration use

  3. enable HTTPS/TLS within Spring Boot

  4. optionally implement an HTTP to HTTPS redirect

2. HTTP Access

I have cloned the "noauth-security-example" to form the "https-hello-example" and left most of the insides intact. You may remember the ability to execute the following authenticated command.

Example Successful Authentication
$ curl -v -X GET http://localhost:8080/api/authn/hello?name=jim -u user:password (1)
> GET /api/authn/hello?name=jim HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwYXNzd29yZA== (2)
>
< HTTP/1.1 200
hello, jim
1 curl supports credentials with -u option
2 curl Base64 encodes credentials and adds Authorization header

We get rejected when no valid credentials are supplied.

Example Rejection of Anonymous
$ curl -X GET http://localhost:8080/api/authn/hello?name=jim
{"timestamp":"2020-07-18T14:43:39.670+00:00","status":401,
 "error":"Unauthorized","message":"Unauthorized","path":"/api/authn/hello"}

It works as we remember it, but the issue is that our Base64 encoded (dXNlcjpwYXNzd29yZA==), plaintext credentials were issued in the clear.

Base64 Encoding is not Encryption
> Authorization: Basic dXNlcjpwYXNzd29yZA== (1)
...
$ echo -n dXNlcjpwYXNzd29yZA== | base64 -d && echo (2)
user:password
1 credentials are base64 encoded
2 base64 encoded credentials can be easily decoded

We can fix that by enabling HTTPS.

3. HTTPS

Hypertext Transfer Protocol Secure (HTTPS) is an extension of HTTP encrypted with Transport Layer Security (TLS) for secure communication between endpoints — offering privacy and integrity (i.e., hidden and unmodified). HTTPS formerly offered encryption with the now deprecated Secure Sockets Layer (SSL). Although the SSL name still sticks around, TLS is only supported today. [1] [2]

3.1. HTTPS/TLS

At the heart of HTTPS/TLS are X.509 certificates and the Public Key Infrastructure (PKI). Public keys are made available to describe the owner (subject), the issuer, and digital signatures that prove the contents have not been modified. If the receiver can verify the certificate and trusts the issuer — the communication can continue. [3]

With HTTPS/TLS, there is a one-way and two-way option with one-way being the most common. In one-way TLS — only the server contains a certificate and the client is anonymous at the network level. Communications can continue if the client trusts the certificate presented by the server. In two-way TLS, the client also presents a signed certificate that can identify them to the server and form two-way authentication at the network level. Two-way is very secure but not as common except in closed environments (e.g., server-to-server environments with fixed/controlled communications). We will stick to one-way TLS in this lecture.

3.2. Keystores

A keystore is a repository of security certificates - both private and public keys. There are two primary types: Public Key Cryptographic Standards (PKCS12) and Java KeyStore (JKS). PKCS12 is an industry standard and JKS is specific to Java. [4] They both have the ability to store multiple certificates and use an alias to identify them. Both use password protection.

There are typically two uses for keystores: your identity (keystore) and the identity of certificates you trust (truststore). The former is used by servers and must be well protected. The latter is necessary for clients. The truststore can be shared but its contents need to be trusted.

3.3. Tools

There are two primary tools when working with certificates and keystores: keytool and openssl.

keytool comes with the JDK and can easily generate and manage certificates for Java applications. Keytool originally used the JKS format but since Java 9 switched over to PKCS12 format.

openssl is a standard, open source tool that is not specific to any environment. It is commonly used to generate and convert to/from all types of certificates/keys.

3.4. Self Signed Certificates

The words "trust" and "verify" were used a lot in the paragraphs above when describing certificates.

When we visit various websites — that locked icon next to the "https" URL indicates the certificate presented by the server was verified and came from a trusted source. Only a server with the associated private key could have generated an inspected value that matched the well-known public key.

Verified Server Certificate

https verified cert

Trusted certificates come from sources that are pre-registered in the browsers and Java JRE truststore and are obtained through purchase.

We can generate self-signed certificates that are not immediately trusted until we either ignore checks or enter them into our local browsers and/or truststore(s).

4. Enable HTTPS/TLS in Spring Boot

To enable HTTPS/TLS in Spring Boot — we must do the following

  1. obtain a digital certificate - we will generate a self-signed certificate without purchase or much fanfare

  2. add TLS properties to the application

  3. optionally add an HTTP to HTTPS redirect - useful in cases where clients forget to set the protocol to https:// and use http:// or use the wrong port number.

4.1. Generate Self-signed Certificate

The following example shows the creation of a self-signed certificate using keytool. Refer to the keytool reference page for details on the options. The following Java Keytool page provides examples of several use cases. However, those references do not include coverage of the now necessary -ext x509 option for -genkeypair and Subject Alternative Name (SAN). The specification of the SAN has to be added to your command.

I kept the values of the certificate extremely basic since there is little chance we will ever use this in a trusted environment. Some key points:

  • we request that an RSA certificate be generated

  • the certificate should be valid for 10 years

  • stored in a keystore ("keystore.p12") using alias ("https-hello")

  • tracked by a DN ("CN=localhost,…​")

  • supplied with a Subject Alternative Name (SAN) X.509 extension to include a DNS ("localhost") and IP ("127.0.0.1") value that the server can legally respond from to be accepted. SAN is a relatively new requirement and takes the place of validating the hostname using Common Name value ("CN=localhost") of the DN.

Generate Self-signed RSA Certificate
$ keytool -genkeypair -keyalg RSA -keysize 2048 -validity 3650 \(1)
-keystore keystore.p12 -storepass password -alias https-hello \(2)
-ext "SAN:c=DNS:localhost,IP:127.0.0.1" \(3)
-dname "CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown" (4)
1 specifying a valid date 10 years (3650 days) in the future
2 assigning the alias https-hello to what is generated in the keystore
3 assigning Subject Alternative Names that host can legally answer with
4 assigning a Distinguished Name to uniquely track the certificate

4.1.1. Inspecting Keystore

Listing Contents of Keystore
$ keytool --list -keystore ./keystore.p12 -storepass password -v
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: https-hello
Creation date: Jul 19, 2025
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Issuer: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Serial number: d5185c6afe082c7d
Valid from: Sat Jul 19 08:18:58 EDT 2025 until: Tue Jul 17 08:18:58 EDT 2035
Certificate fingerprints:
	 SHA1: 3B:C2:5C:AE:03:BC:6F:F4:38:C1:DE:BE:59:88:94:5E:8D:5A:80:D2
	 SHA256: 9C:EB:E2:40:9C:EA:A0:DF:E4:91:1A:D9:59:FE:B8:54:BB:78:DF:CF:57:85:46:8B:F8:21:7D:8B:AB:57:46:F5
Signature algorithm name: SHA384withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.17 Criticality=true
SubjectAlternativeName [
  DNSName: localhost
  IPAddress: 127.0.0.1
]

#2: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 60 6B 0E 74 71 1D 90 A3   95 2D E9 9D 4F 10 E4 87  `k.tq....-..O...
0010: F5 4E 44 AF                                        .ND.
]
]
I was alerted to the X.509 feature as a new requirement/solution via the following Stackoverflow post. Prior versions of Spring Boot networking dependencies did not have that extension as a resolution requirement.

The following Keytool Cheatsheet page makes a nice reference once you understand the importance of SAN.

4.2. Create Client Truststore

The whole point of HTTPS is to provide data privacy and trust in who you are communicating with. To ensure trust, our Java test client will need a copy of the server’s certificate and must be stored in a "truststore". The truststore is the same technical container construct as the keystore. The only difference is how we use them. Since this is just a test, we could simply use the certificate directly from the keystore. However, let’s go an extra step to separate the public information from the private information using a separate truststore.

4.2.1. Export Public Certificate

Start by exporting the public certificate. This is information the client uses to verify the server is something trusted to communicate with. It will contain address and key signature information that only the trusted server can satisfy.

Extract Public Certificate from KeyStore
keytool -exportcert -rfc \
-keystore keystore.p12 -alias https-hello -storepass password \
-file https-hello.crt

By default, the export will be in binary format. By adding the -rfc parameter, we get a portable PEM format.

$ cat https-hello.crt
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIJANUYXGr+CCx9MA0GCSqGSIb3DQEBDAUAMG4xEDAOBgNV
BAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vua25vd24x
...
AfKRByxJS5UYNMiV+NJe3js+UBE9bHCFINFYTWh9k8LEyUPU+DSX/kq8/KkKKaRa
qn1kz/u9MsgoT/flRLInwY59hw==
-----END CERTIFICATE-----

4.2.2. Import Certificate into Truststore

We add the self-signed certificate to our client’s truststore using the following command.

Import Certificate into Client Truststore
keytool -import -file https-hello.crt -noprompt -alias https-hello \
-keystore truststore.p12 -storepass trustpass

If the truststore does not already exist, it will be created. Java requires a keystore and truststore to have a password. The -noprompt parameter turns off verification prompts.

4.2.3. Inspecting Truststore

After importing/creating, we can use the -list command to inspect the contents of the truststore.

Listing Entries in Truststore
$ keytool -list -keystore truststore.p12 -storepass trustpass
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

https-hello, Jul 20, 2025, trustedCertEntry,
Certificate fingerprint (SHA-256): 9C:EB:E2:40:9C:EA:A0:DF:E4:91:1A:D9:59:FE:B8:54:BB:78:DF:CF:57:85:46:8B:F8:21:7D:8B:AB:57:46:F5

Details can be shown by adding the verbose (-v) parameter.

Listing Contents of Truststore
$ keytool --list -keystore ./truststore.p12 -storepass trustpass -v
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: https-hello
Creation date: Jul 20, 2025
Entry type: trustedCertEntry

Owner: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Issuer: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Serial number: d5185c6afe082c7d
Valid from: Sat Jul 19 08:18:58 EDT 2025 until: Tue Jul 17 08:18:58 EDT 2035
Certificate fingerprints:
	 SHA1: 3B:C2:5C:AE:03:BC:6F:F4:38:C1:DE:BE:59:88:94:5E:8D:5A:80:D2
	 SHA256: 9C:EB:E2:40:9C:EA:A0:DF:E4:91:1A:D9:59:FE:B8:54:BB:78:DF:CF:57:85:46:8B:F8:21:7D:8B:AB:57:46:F5
Signature algorithm name: SHA384withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.17 Criticality=true
SubjectAlternativeName [
  DNSName: localhost
  IPAddress: 127.0.0.1
]

#2: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 60 6B 0E 74 71 1D 90 A3   95 2D E9 9D 4F 10 E4 87  `k.tq....-..O...
0010: F5 4E 44 AF                                        .ND.
]
]

4.3. Place Keystore and Truststore in Reference-able Location

I placed the keystore and truststore in the resources area of the application — which will be easily referenced at runtime using a classpath reference.

Place Keystore in Location to be Referenced
$ tree src/main/resources/demo-pki/
src/main/resources/demo-pki/
|-- keystore.p12
`-- truststore.p12
Incremental Learning Example Only: Don’t use Source Tree for Certs

This example is trying hard to be simple and using a classpath for the keystore to be portable. You should already know how to convert the classpath reference to a file or other reference to keep sensitive information protected and away from the code base. Do not store credentials or other sensitive information in the src tree in a real application as the src tree will be stored in CM.

4.4. Add TLS properties

The following shows a minimal set of properties needed to enable TLS. [5]

Example TLS properties
server.port=8443(1)
server.ssl.enabled=true
server.ssl.key-store=classpath:demo-pki/keystore.p12(2)
server.ssl.key-store-password=password(3)
server.ssl.key-alias=https-hello
1 using an alternate port - optional
2 referencing keystore in the classpath — could also use a file reference
3 think twice before placing credentials in a properties file
Do not place credentials in CM system
Do not place real credentials in files checked into CM. Have them resolved from a source provided at runtime.
Note the presence of the legacy "ssl" term in the property name even though "ssl" is deprecated, and we are technically setting up "tls".

5. Untrusted Certificate Error

Once we restart the server, we should be able to connect using HTTPS and port 8443. However, there will be a trust error. The following shows the error from curl.

Untrusted Certificate Error
$ curl https://localhost:8443/api/authn/hello?name=jim -u user:password
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

5.1. Option: Supply Trusted Certificates

One option is to construct a file of trusted certificates that includes our self-signed certificate. Curl requires this to be in PEM format.

We can use the PEM certificate file ("https-hello.crt") that we used to build the truststore. In the following snippet, I am referencing the certificate in the target directory following a build.

Supply Server Certificate
$ curl https://localhost:8443/api/authn/hello?name=jim -u user:password \
--cacert target/classes/demo-pki/https-hello.crt -v

...
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: target/classes/demo-pki/https-hello.crt
...
* Server certificate:
*  subject: C=Unknown; ST=Unknown; L=Unknown; O=Unknown; OU=Unknown; CN=localhost
*  start date: Jul 19 12:18:58 2025 GMT
*  expire date: Jul 17 12:18:58 2035 GMT
*  subjectAltName: host "localhost" matched cert's "localhost"
*  issuer: C=Unknown; ST=Unknown; L=Unknown; O=Unknown; OU=Unknown; CN=localhost
*  SSL certificate verify ok.
...
< HTTP/1.1 200
hello, jim
The PEM files can be concatenated together into a single file if you need to have multiple certificates in the cacert file.

5.1.1. Alternative: Build Certificate with openssl

Of note, the certificate PEM can also be built using the openssl command — which contains a "Swiss Army Knife" of PKI functions. We can build that with the openssl pkcs12 command using either the keystore or truststore.

Building Example Trusted Certificate Authority
openssl pkcs12 -in ./src/main/resources/demo-pki/keystore.p12 -passin pass:password \
-out ./target/certs.pem \
-clcerts -nokeys -nodes (1)
1 certs only, no private keys, not password protected

5.2. Option: Accept Self-signed Certificates

curl and browsers have the ability to accept self-signed certificates either by ignoring their inconsistencies or adding them to their truststore.

The following is an example of curl’s -insecure option (-k abbreviation) that will allow us to communicate with a server presenting a certificate that fails validation.

Enable -insecure Option
$ curl -kv -X GET https://localhost:8443/api/authn/hello?name=jim -u user:password
...
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
* Server certificate:
*  subject: C=Unknown; ST=Unknown; L=Unknown; O=Unknown; OU=Unknown; CN=localhost
*  start date: Jul 19 12:18:58 2025 GMT
*  expire date: Jul 17 12:18:58 2035 GMT
*  issuer: C=Unknown; ST=Unknown; L=Unknown; O=Unknown; OU=Unknown; CN=localhost
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
...
< HTTP/1.1 200
hello, jim

6. Optional Redirect

To handle clients that may address our application using the wrong protocol or port number — we can optionally set up a redirect to go from the common port to the TLS port. The following snippet was taken directly from a ZetCode article, but I have seen this near exact snippet many times elsewhere.

HTTP:8080 ⇒ HTTPS:8443 Redirect
@Bean
public ServletWebServerFactory servletContainer() {
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
        @Override
        protected void postProcessContext(Context context) {
            SecurityConstraint securityConstraint = new SecurityConstraint();
            securityConstraint.setUserConstraint("CONFIDENTIAL");

            SecurityCollection collection = new SecurityCollection();
            collection.addPattern("/*");
            securityConstraint.addCollection(collection);
            context.addConstraint(securityConstraint);
        }
    };

    tomcat.addAdditionalTomcatConnectors(redirectConnector());
    return tomcat;
}

private Connector redirectConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    connector.setScheme("http");
    connector.setPort(8080);
    connector.setSecure(false);
    connector.setRedirectPort(8443);
    return connector;
}

6.1. HTTP:8080 ⇒ HTTPS:8443 Redirect Example

With the optional redirect in place, the following shows an example of the client being sent from their original http://localhost:8080 call to https://localhost:8443.

$ curl -kv -X GET http://localhost:8080/api/authn/hello?name=jim -u user:password
> GET /api/authn/hello?name=jim HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwYXNzd29yZA==
>
< HTTP/1.1 302 (1)
< Location: https://localhost:8443/api/authn/hello?name=jim (2)
1 HTTP 302/Redirect Returned
2 Location header provides the full URL to invoke — including the protocol

6.2. Follow Redirects

Browsers automatically follow redirects, and we can get curl to automatically follow redirects by adding the --location option (or -L abbreviated). However, for security reasons, this will not present the credentials to the redirected location.

Adding --location-trusted will perform the --location redirect and present the supplied credentials to the new location — with security risk that we did not know the location ahead of time.

The following command snippet shows curl being requested to connect to an HTTP port , receiving a 302/Redirect, and then completing the original command using the URL provided in the Location header of the redirect.

Example curl Follow Redirect
$ curl -kv -X GET http://localhost:8080/api/authn/hello?name=jim -u user:password --location-trusted (1)
...
* Server auth using Basic with user 'user'
> GET /api/authn/hello?name=jim HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwYXNzd29yZA==
...
< HTTP/1.1 302
< Location: https://localhost:8443/api/authn/hello?name=jim
...
* Issue another request to this URL: 'https://localhost:8443/api/authn/hello?name=jim'
...
* Server certificate:
*  subject: C=Unknown; ST=Unknown; L=Unknown; O=Unknown; OU=Unknown; CN=localhost
...
*  issuer: C=Unknown; ST=Unknown; L=Unknown; O=Unknown; OU=Unknown; CN=localhost
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
...
> GET /api/authn/hello?name=jim HTTP/1.1
> Host: localhost:8443
> Authorization: Basic dXNlcjpwYXNzd29yZA==
...
< HTTP/1.1 200
hello, jim
1 --location-trusted redirect option causes curl to follow the 302/Redirect response and present credentials
It is a security risk to (a) send credentials using an unencrypted HTTP connection and then (b) have the credentials issued to a location issued by the server. This was just an example of how to implement server-side redirects and perform a simple test/demonstration. This was not an example of how to securely implement the client-side.

6.3. Caution About Redirects

One note of caution I will give about redirects is the tendency for Intellij to leave orphan processes which seems to get worse with the Tomcat redirect in place. Since our targeted interfaces are for API clients — which should have a documented source of how to communicate with our server — there should be no need for the redirect. The redirect is primarily valuable for interfaces that switch between HTTP and HTTPS. We are either all HTTP or all HTTPS and no need to be eclectic.

7. Maven Unit Integration Test

The approach to unit integration testing the application with HTTPS enabled is very similar to non-HTTPS techniques. The changes will be primarily in enhancing the ClientHttpRequestFactory that we have been defaulting to a simple HTTP version to date.

Former ClientHttpRequestFactory Bean Factory being Replaced
@Bean
public ClientHttpRequestFactory requestFactory() {
    return new SimpleClientHttpRequestFactory();
}

We will be enhancing the ClientHttpRequestFactory @Bean factory with an optional SSLFactory to control what gets created. @Autowired(required=false) is used to make the injected component optional/nullable.

Enhanced ClientHttpRequestFactory Bean Factory with Optional SSLFactory
@Bean
public ClientHttpRequestFactory httpsRequestFactory(
        @Autowired(required = false) SSLFactory sslFactory) { ...

We will support that method with a conditional SSLFactory @Bean factory that will be activated when it.server.trust-store property is supplied and non-empty. @ConditionalOnExpression is used with a Spring Expression Language (SpEL) expression designed to fail if the property is not provided or provided without a value.

Conditional SSLFactory
@Bean
@ConditionalOnExpression(
    "!T(org.springframework.util.StringUtils).isEmpty('${it.server.trust-store:}')")
public SSLFactory sslFactory(ResourceLoader resourceLoader, ServerConfig serverConfig)
    throws IOException { ...

7.1. JUnit @SpringBootTest

The @SpringBootTest will bring in the proper configuration and activate:

  • the application’s https profile to activate HTTPS on the server-side

  • the tests ntest profile to add any properties needed by the @SpringBootTest

@SpringBootTest(classes= {ClientTestConfiguration.class}, (1)
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles({"https", "ntest"}) (2)
@Slf4j
public class HttpsRestTemplateNTest {
    @Autowired
    private RestTemplate authnUser;
    @Autowired
    private URI authnUrl;
1 test configuration with test-specific components
2 activate application https profile and testing ntest profile

It is important to note that using HTTPS is not an intrusive part of the application or test. If we eliminate the https and ntest profiles, the tests will complete using straight HTTP.

7.2. application-https.properties [REVIEW]

The following is a repeat from above of what the application has defined for the https profile.

application-https.properties
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:demo-pki/keystore.p12
server.ssl.key-store-password=password
server.ssl.key-alias=https-hello

7.3. application-ntest.properties

The following snippet shows the ntest profile-specific configuration file used to complete the test definition. It primarily supplies the fact that:

  • the protocol scheme will be HTTPS

  • trustStore properties need for the client-side of communication

For convenience and consistency, I have defined the ntest property values using the https property values.

application-ntest.properties
it.server.scheme=https
#using trustore
it.server.trust-store=classpath:demo-pki/truststore.p12 (1)
it.server.trust-store-password=trustpass (1)

#option to use keystore
#it.server.trust-store=${server.ssl.key-store} (2)
#it.server.trust-store-password=${server.ssl.key-store-password} (2)
1 using trustore
2 option to use keystore properties defined in application.properties
The keystore/truststore used in this example is for learning and testing. Do not store operational certs in the source tree. Those files end up in the searchable CM system, and the JARs with the certs end up in a Nexus repository.

7.4. ServerConfig

For brevity, I will not repeat the common constructs seen in most API and security test configurations. However, the first quiet change to the @TestConfiguration is the definition of the ServerConfig. It will be done in two stages.

  • property population

  • server port# assignment

The first stage gives us a chance to assemble the static properties provided to the JVM (e.g., truststore) below it.server at startup. This bean does not require @Lazy.

ServerConfig Properties
@Bean
@ConfigurationProperties("it.server") (1)
public ServerConfig serverConfig() {
    return new ServerConfig();
}
1 @ConfigurationProperties (scheme=https, truststore, and password) are applied to what is returned

The second stage provides us with a ServerConfig augmented with the test server port# instantiated at runtime. This bean and all beans that depend on it are required to be @Lazy.

TestServer ServerConfig
@Bean @Lazy
public ServerConfig testServerConfig(ServerConfig serverConfig, (1)
        @LocalServerPort int port) { (2)
    return serverConfig.withBaseUrl(null).withPort(port).build(); (3)
}
1 start with a ServerConfig populated with static properties
2 @LocalServerPort is assigned after server/test startup
3 assign port# and reset baseUrl to assure ServerConfig cached URL is rebuilt

The two @Bean factories are using the same ServerConfig type but qualifying that using different names (serverConfig and testServerConfig). Injections will use the different names as a qualifier.

7.5. Maven Dependencies

Spring 6 updated the networking in RestTemplate to use httpclient5 and a custom SSL Context library for HTTPS communications. httpclient5 and its TLS extensions require two new dependencies for our test client to support this extra setup.

Spring 6 RestTemplate Required HTTPS Dependencies
<!-- needed to set up TLS for RestTemplate -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-apache5</artifactId>
    <scope>test</scope>
</dependency>

7.6. HTTPS ClientHttpRequestFactory

The HTTPS-based ClientHttpRequestFactory is built by following basic instructions in the docs and then further documentation to avoid deprecated methods. This approach was tweaked some to make TLS a runtime option. There are three main objects created:

  1. connectionManager using the optional sslFactory. If present, the sslFactory must be made available non-@Lazy

  2. httpClient using the connectionManager

  3. requestFactory using the httpClient. This is what is returned.

ClientHttpRequestFactory @Bean Factory with Optional SSL Support
import nl.altindag.ssl.util.Apache5SslUtils;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;

/*
https://sslcontext-kickstart.com/client/apache5.html
https://github.com/Hakky54/sslcontext-kickstart#apache-5
 */
@Bean
public ClientHttpRequestFactory httpsRequestFactory(
        @Autowired(required = false) SSLFactory sslFactory) { (1)
    PoolingHttpClientConnectionManagerBuilder builder =
            PoolingHttpClientConnectionManagerBuilder.create();
    PoolingHttpClientConnectionManager connectionManager =
      Optional.ofNullable(sslFactory)
        //.map(sf->builder.setSSLSocketFactory(Apache5SslUtils.toSocketFactory(sf))) // deprecated (4)
        .map(sf->builder.setTlsSocketStrategy((TlsSocketStrategy)
                ClientTlsStrategyBuilder.create()
                    .setSslContext(sf.getSslContext())
                    .build())) (2)
        .orElse(builder) (3)
        .build();

    HttpClient httpsClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .build();
    return new HttpComponentsClientHttpRequestFactory(httpsClient);
}
1 @Autowired.required=false allows sslFactory to be null if not using SSL
2 Optional processing path if sslFactory is present
3 Optional processing path if sslFactory is not present
4 deprecated early option to setTlsSocketStrategy()

7.7. SSL Factory

The SSLFactory @Bean factory is conditional based on the presence of a non-empty it.server.trust-store property. If that property does not exist or has an empty value — this @Bean factory will not be invoked and the ClientHttpRequestFactory @Bean factory will be invoked with a null for the sslFactory.

The trustStore is loaded with the help of the Spring ResourceLoader. By using this component, we can use classpath:, file:, and other resource location syntax in order to construct an InputStream to ingest the trustStore. The ResourceLoader is also useful to load resources packaged in JAR.

Conditional SSLFactory @Bean Factory based on Present Trust Store Property
import nl.altindag.ssl.SSLFactory;
import org.springframework.core.io.ResourceLoader;

@Bean
@ConditionalOnExpression(
        "!T(org.springframework.util.StringUtils).isEmpty('${it.server.trust-store:}')") (1)
public SSLFactory sslFactory(ResourceLoader resourceLoader, ServerConfig serverConfig)
        throws IOException {
    try (InputStream trustStoreStream = resourceLoader
            .getResource(serverConfig.getTrustStore()).getInputStream()) {
        return SSLFactory.builder()
            .withProtocols("TLSv1.2")
            .withTrustMaterial(trustStoreStream, serverConfig.getTrustStorePassword())
            .build();
    } catch (FileNotFoundException ex) {
        throw new IllegalStateException("unable to locate truststore: " + serverConfig.getTrustStore(), ex);
    }
}
1 conditional activates factory bean when it.server.trust-store is not empty

7.8. JUnit @Test

The core parts of the JUnit test are pretty basic once we have the HTTPS/Authn-enabled RestTemplate and baseUrl injected.

Review: Standard RestTemplate Construction used
@Bean
public RestTemplate authnUser(RestTemplateBuilder builder,
                              ClientHttpRequestFactory requestFactory) {
    RestTemplate restTemplate = builder.requestFactory(
            //used to read streams twice -- enable use of logging filter below
            ()->new BufferingClientHttpRequestFactory(requestFactory))
            .interceptors(new BasicAuthenticationInterceptor(username, password),
                    new RestTemplateLoggingFilter())
            .build();
    return restTemplate;
}
Review: Standard URI Construction used (with qualifier)
@Bean @Lazy
public URI authnUrl(ServerConfig testServerConfig) {
    URI baseUrl = testServerConfig.getBaseUrl();
    log.info("baseUrl={}", baseUrl);
    return UriComponentsBuilder.fromUri(baseUrl).path("/api/authn/hello").build().toUri();
}

From here it is just a normal test, but activity is remote on the server side.

Review: Example Test
public class HttpsRestTemplateNTest {
    @Autowired (1)
    private RestTemplate authnUser;
    @Autowired (2)
    private URI authnUrl;

    @Test
    public void user_can_call_authenticated() {
        //given a URL to an endpoint that accepts only authenticated calls
        URI url = UriComponentsBuilder.fromUri(authnUrl)
            .queryParam("name", "jim").build().toUri();

        //when called with an authenticated identity
        ResponseEntity<String> response = authnUser.getForEntity(url, String.class);

        //then expected results returned
        then(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        then(response.getBody()).isEqualTo("hello, jim");
    }
}
1 RestTemplate with authentication and HTTPS aspects addressed using filters
2 authnUrl built from ServerConfig and injected into test
Example HTTPS Test Execution
INFO  ClientTestConfiguration#authnUrl:52 baseUrl=https://localhost:60364
INFO  HttpsRestTemplateNTest#setUp:30 baseUrl=https://localhost:60364/api/authn/hello
DEBUG RestTemplate#debug:127 HTTP GET https://localhost:60364/api/authn/hello?name=jim

With the https and ntest profiles disabled, the test reverts to HTTP.

Example HTTPS Test Execution
INFO  ClientTestConfiguration#authnUrl:52 baseUrl=http://localhost:60587
INFO  HttpsRestTemplateNTest#setUp:29 baseUrl=http://localhost:60587/api/authn/hello
DEBUG RestTemplate#debug:127 HTTP GET http://localhost:60587/api/authn/hello?name=jim

8. Summary

In this module, we learned:

  • the basis of how HTTPS forms trusted, private communications

  • how to generate a self-signed certificate for demonstration use

  • how to enable HTTPS/TLS within our Spring Boot application

  • how to add an optional redirect and why it may not be necessary


1. "HTTPS", Wikipedia
2. "Transport Layer Security", Wikipedia
3. "Public key certificate", Wikipedia
4. "Spring Boot HTTPS", ZetCode, July 2020
5. "HTTPS using Self-Signed Certificate in Spring Boot", Baeldung, June 2020