SSL (or its successor TLS) is a communication protocol used to transfer encrypted data over a network. It involves Authentication of the entities and Encryption/Decryption of data being shared between a web server and client.
This article is about SSL and how we can setup it on a springboot application.
Authentication
Authentication of entities on a network is established using digital certificate. Typically in a one way handshake, the server has a keystore which holds a private key, a public key and a x509 certificate where the public and private keys form a pair, private key is used for decryption and public key is used for encryption of data.
There is a truststore which stores all trusted certificates signed by CA. Client/Server refers to it in order to validate a certificate.
When a client sends a request to server, the server responds back with the certificate and the public key.
Client authenticates the server by validating if the certificate received is present in truststore or not. In many cases the server would also validate its client that process is referred to as two way handshake.
Encryption
Once the server is found authentic the client generates a shared key and encrypts it using server’s public key. It sends the encrypted shared key to the server where the server decrypts it using its private key. Now both server and client have a shared session key which can be used for encryption and decryption of data being transferred between them.
The server presents its public key and certificates from keystore to the client, the client validates the certificate is present in truststore so that the server can be trusted.
Setting up SSL in spring boot application
Generating the certificates
Here’s how you can generate your own keystore/trustore pair in jks format using openssl.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# Generate a private key
openssl genrsa -des3 -out privatekey.key 1024
# Generate Certificate signeding Request (CSR)
# CN should be the domain-name or localhost for running in local
openssl req -new -key privatekey.key -out csrfile.csr
# Generate self signed certificate
openssl x509 -req -days 365 -in csrfile.csr -signkey privatekey.key -out public.crt
# Import the self signed certificate to a truststore file
keytool -import -file public.crt -alias exampleCA -keystore truststore.jks
# Import the keypair and certificates to a keystore file
openssl pkcs12 -export -in public.crt -inkey privatekey.key -certfile public.crt -name "certs" -out keystore.p12
# to convert p12 to jks
keytool -importkeystore -srckeystore keystore.p12 -srcstoretype pkcs12 -destkeystore keystore.jks -deststoretype JKS
|
Configuring the server
Once the certificates are generated copy the truststore and keystore .jks files to the classpath of your spring boot application.
Here I am using springboot 2.3.4.RELEASE
, spring-security dependency is required in the pom file.
1
2
3
4
5
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
|
Add a WebSecurityConfig
extending WebSecurityConfigurerAdapter
class to allow request on https and block any request coming from a non-secure HTTP channel.
1
2
3
4
5
6
7
8
9
10
11
|
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel()
.anyRequest()
.requiresSecure();
}
}
|
Update the spring application configuration file as below.
1
2
3
4
5
6
7
8
9
10
|
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.jks
key-store-password: password
trust-store: classpath:truststore.jks
trust-store-password: password
client-auth: need
|
Test it out
Create one example controller:
1
2
3
4
5
6
7
8
9
|
@RestController
@RequestMapping("/ssl")
public class ExampleController {
@RequestMapping(path="/example", method= RequestMethod.GET)
public String apiRequest() {
return "Successfully Validated!";
}
}
|
In the application tests, create a RestClient configuration which would access the server with ssl.
SSLContextBuilder
and HttpClients
are part of apache httpclients library.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@Configuration
public class RestClientTest {
private String password = "password";
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"),
password.toCharArray(), password.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), password.toCharArray())
.build();
return builder
.requestFactory(
()->new HttpComponentsClientHttpRequestFactory(
HttpClients.custom()
.setSSLContext(sslContext)
.build()
)
)
.build();
}
}
|
A sample junit test to test the endpoint. Note the host url should have https
protocol or it will fail during SSL handshake.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public class ApplicationTests {
public ApplicationTests(){
}
@LocalServerPort
private int port;
@Autowired
private RestTemplate restTemplate;
@Test
public void withSSL() {
String response = restTemplate.getForObject("https://localhost:" + port + "/ssl/example", String.class);
Assert.assertEquals("Successfully Validated!", response);
}
}
|