{{announcement.body}}
{{announcement.title}}

SSL-Based/Secured FeignClient Example in Java Microservices With Eureka

DZone 's Guide to

SSL-Based/Secured FeignClient Example in Java Microservices With Eureka

In this article, I will explain how to develop a Feign Client application/service to establish two way SSL based communication.

· Microservices Zone ·
Free Resource

Prerequisite:

  • You should have one Discovery Server up and running
  • An SSL based microservice registered with the above Discovery Server up and running which we are going to consume from below FeignClient
  • You need to generate your client application's keystore and trust store. You need to add your server certificate into your client application's trustStore and add your client application's certificate in the Server application's trustStore.

Before reading this article make sure that you know about Spring Feign Client. In this article, I will explain how to develop a Feign Client application/service to establish two way SSL based communication. In simple words, how a Feign Client can make an HTTPS REST service call. Also for this project, I assumed that we have a Eureka discovery server and one SSL based Spring boot microservice called global-repository. In other words from our FeignClient below, we are going to consume an SSL based microservice (global-repository)with a valid certificate.

Step 1. Click https://start.spring.io/ and open Spring initializr. Now do exactly what I am doing in the below screenshot. (Group, Artifact, etc fill as per your choice)

Now import the project to your eclipse/Intellij

First, add the below two lines of codes into your application class FeignClientDemoApplication.

@EnableFeignClients

@EnableDiscoveryClient

In our case it should look like:

Java
 




xxxxxxxxxx
1


 
1
@SpringBootApplication
2
 
          
3
@EnableFeignClients
4
 
          
5
@EnableDiscoveryClient
6
 
          
7
public class FeignClientDemoApplication {


Now rename application.properties as application.yml and modify it like below(if you are not using a discovery server please do not use eureka section below and remove @EnableDiscoveryClient from FeignClientDemoApplication.java and also remove the dependency from your pom.xml):

XML
 




xxxxxxxxxx
1
23


1
server.port: 9909
2
spring:
3
application.name: feign-client-demo
4
eureka:
5
client:
6
serviceUrl:
7
defaultZone: ${EUREKA_URI:http://localhost:9001/eureka}
8
registryFetchIntervalSeconds: 1
9
instance:
10
leaseRenewalIntervalInSeconds: 1


Step 2: Create a new java class called SSLUtil for SSL configuration and modify it like below:

Java
 




xxxxxxxxxx
1
274


 
1
package com.example.bhaiti.assam.feignclientdemo;
2
 
          
3
import java.io.FileInputStream;
4
import java.io.FileNotFoundException;
5
import java.io.IOException;
6
import java.security.KeyManagementException;
7
import java.security.KeyStore;
8
import java.security.KeyStoreException;
9
import java.security.NoSuchAlgorithmException;
10
import java.security.SecureRandom;
11
import java.security.UnrecoverableKeyException;
12
import java.security.cert.CertificateException;
13
import javax.net.ssl.KeyManagerFactory;
14
import javax.net.ssl.SSLContext;
15
import javax.net.ssl.SSLSocketFactory;
16
import javax.net.ssl.TrustManagerFactory;
17
import javax.net.ssl.TrustManager;
18
import javax.net.ssl.X509TrustManager;
19
import java.security.cert.X509Certificate;
20
 
          
21
import javax.net.ssl.KeyManager;
22
 
          
23
public class SSLUtil {
24
 
          
25
    private static TrustManager[] trustAllCerts = null;
26
    private static String keymanageralgorithm = null;
27
    private static SSLContext sslContext = null;
28
    /*
29
    static {
30
        //for localhost testing only
31
        javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
32
        new javax.net.ssl.HostnameVerifier(){
33
 
          
34
            public boolean verify(String hostname,
35
                    javax.net.ssl.SSLSession sslSession) {
36
                if (hostname.equals("localhost")) {
37
                    return true;
38
                }
39
                return false;
40
            }
41
        });
42
    }*/
43
 
          
44
    public static void setupSslContext() {
45
 
          
46
        boolean trustall = false;
47
        
48
        try {
49
            String keyStorePath = "C:\\Users\\prate\\ssl\\client\\keyStore.jks";
50
            String trustStorePath = "C:\\Users\\prate\\ssl\\client\\trustStore.jks";
51
            String keyStorePw = "deva1972";
52
            String trustStorePw = "deva1972";
53
            String keyPass = "deva1972";
54
            String trustAllCertificate = "False";
55
            String keystoreType = "JKS";
56
            keymanageralgorithm = "SunX509"; // For IBM it should be IbmX509
57
            trustAllCerts = new TrustManager[] { new X509TrustManager() {
58
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
59
                    return null;
60
                }
61
 
          
62
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
63
                }
64
 
          
65
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
66
                }
67
 
          
68
            } };
69
            if (trustAllCertificate.equalsIgnoreCase("True")) {
70
                trustall = true;
71
            }
72
            if (keystoreType.equalsIgnoreCase("JKS"))
73
                sslContext = initializeSSLContext(keyStorePath, keyStorePw, trustStorePath, trustStorePw, keyPass,
74
                        trustall);
75
            else
76
                sslContext = initializeSSLContextP12Cert(keyStorePath, keyStorePw, trustStorePath, trustStorePw,
77
                        keyPass, trustall);
78
 
          
79
        } catch (Exception exp) {
80
            System.out
81
                    .println("ConfigException exception occurred while reading the config file : " + exp.getMessage());
82
            exp.printStackTrace();
83
        }
84
    }
85
    
86
    public static SSLSocketFactory getClientSSLSocketFactory() {
87
        if(sslContext != null) {
88
            return sslContext.getSocketFactory();
89
        }
90
        return null;
91
    }
92
 
          
93
    /**
94
     * 
95
     * @param keyStorePath
96
     * @param pwKeyStore
97
     * @param trustStorePath
98
     * @param pwTrustStore
99
     * @param keyPass
100
     * @return
101
     * @throws Exception
102
     */
103
    private static SSLContext initializeSSLContext(final String keyStorePath, final String pwKeyStore,
104
            final String trustStorePath, final String pwTrustStore, final String keyPass, final boolean trustall) {
105
        System.out.println(" In initializeSSLContext");
106
        char[] keyStorePw = pwKeyStore.toCharArray();
107
        char[] trustStorePw = pwTrustStore.toCharArray();
108
        char[] keyPw = keyPass.toCharArray();
109
        SecureRandom secureRandom = new SecureRandom();
110
        secureRandom.nextInt();
111
 
          
112
        KeyStore ks = null;
113
        try {
114
            ks = KeyStore.getInstance("JKS");
115
        } catch (KeyStoreException exp) {
116
            System.out.println("KeyStoreException exception occurred while reading the config file : " + exp.getMessage());
117
        }
118
        FileInputStream fis = null;
119
        try {
120
            try {
121
                fis = new FileInputStream(keyStorePath);
122
            } catch (FileNotFoundException exp) {
123
                System.out.println("FileNotFoundException exception occurred " + exp.getMessage());
124
            }
125
            try {
126
                ks.load(fis, keyStorePw);
127
            } catch (NoSuchAlgorithmException exp) {
128
                System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
129
            } catch (CertificateException exp) {
130
                System.out.println("CertificateException exception occurred " + exp.getMessage());
131
            } catch (IOException exp) {
132
                System.out.println("CertificateException exception occurred " + exp.getMessage());
133
            }
134
        } finally {
135
            if (fis != null)
136
                try {
137
                    fis.close();
138
                } catch (IOException exp) {
139
                    System.out.println("IOException exception occurred " + exp.getMessage());
140
                }
141
        }
142
 
          
143
        System.out.println("[initializeSSLContext] KMF keystorepw loaded.");
144
 
          
145
        KeyManagerFactory kmf = null;
146
        try {
147
            kmf = KeyManagerFactory.getInstance(keymanageralgorithm);
148
        } catch (NoSuchAlgorithmException exp) {
149
            System.out.println("IOException exception occurred " + exp.getMessage());
150
        }
151
        try {
152
            kmf.init(ks, keyPw);
153
        } catch (UnrecoverableKeyException exp) {
154
            System.out.println("UnrecoverableKeyException exception occurred " + exp.getMessage());
155
        } catch (KeyStoreException exp) {
156
            System.out.println("KeyStoreException exception occurred " + exp.getMessage());
157
        } catch (NoSuchAlgorithmException exp) {
158
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
159
        }
160
 
          
161
        System.out.println("[initializeSSLContext] KMF init done.");
162
 
          
163
        KeyStore ts = null;
164
        try {
165
            ts = KeyStore.getInstance("JKS");
166
        } catch (KeyStoreException exp) {
167
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
168
        }
169
        FileInputStream tfis = null;
170
        SSLContext sslContext = null;
171
        try {
172
            tfis = new FileInputStream(trustStorePath);
173
            ts.load(tfis, trustStorePw);
174
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(keymanageralgorithm);
175
            tmf.init(ts);
176
            System.out.println("[initializeSSLContext] Truststore initialized");
177
            sslContext = SSLContext.getInstance("TLS");
178
 
          
179
            if (trustall)
180
                sslContext.init(kmf.getKeyManagers(), trustAllCerts, secureRandom);
181
            else
182
                sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), secureRandom);
183
 
          
184
        } catch (NoSuchAlgorithmException exp) {
185
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
186
        } catch (CertificateException exp) {
187
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
188
        } catch (IOException exp) {
189
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
190
        } catch (KeyStoreException exp) {
191
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
192
        } catch (KeyManagementException exp) {
193
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
194
        } finally {
195
            if (tfis != null)
196
                try {
197
                    tfis.close();
198
                } catch (IOException exp) {
199
                    System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
200
                }
201
        }
202
 
          
203
        if ((sslContext == null)) {
204
            System.out.println("[initializeSSLContext] sslContext is null");
205
            System.exit(-1);
206
        }
207
        return sslContext;
208
    }
209
 
          
210
    /**
211
     * 
212
     * @param keyStorePath
213
     * @param pwKeyStore
214
     * @param trustStorePath
215
     * @param pwTrustStore
216
     * @param keyPass
217
     * @return
218
     * @throws Exception
219
     */
220
    private static SSLContext initializeSSLContextP12Cert(final String keyStorePath, final String pwKeyStore,
221
            final String trustStorePath, final String pwTrustStore, final String keyPass, final boolean trustall) {
222
        System.out.println("In initializeSSLContextP12Cert");
223
        SSLContext sslContext = null;
224
        String keystore = keyStorePath;
225
        String keystorepass = pwKeyStore;
226
        String truststore = trustStorePath;
227
        String truststorepass = pwTrustStore;
228
 
          
229
        try {
230
            KeyStore clientStore = KeyStore.getInstance("PKCS12");
231
            clientStore.load(new FileInputStream(keystore), keystorepass.toCharArray());
232
 
          
233
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(keymanageralgorithm);
234
            kmf.init(clientStore, keystorepass.toCharArray());
235
            KeyManager[] kms = kmf.getKeyManagers();
236
 
          
237
            KeyStore trustStore = KeyStore.getInstance("JKS");
238
            trustStore.load(new FileInputStream(truststore), truststorepass.toCharArray());
239
 
          
240
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(keymanageralgorithm);
241
            tmf.init(trustStore);
242
            TrustManager[] tms = tmf.getTrustManagers();
243
            sslContext = SSLContext.getInstance("TLS");
244
 
          
245
            if (trustall)
246
                sslContext.init(kms, trustAllCerts, new SecureRandom());
247
            else
248
                sslContext.init(kms, tms, new SecureRandom());
249
 
          
250
        } catch (NoSuchAlgorithmException exp) {
251
            System.out.println("NoSuchAlgorithmException exception occurred " + exp.getMessage());
252
        } catch (CertificateException exp) {
253
            System.out.println("CertificateException exception occurred " + exp.getMessage());
254
        } catch (IOException exp) {
255
            System.out.println("IOException occurred while reading the key file  " + exp.getMessage());
256
        } catch (KeyStoreException exp) {
257
            System.out.println("KeyStoreException exception occurred " + exp.getMessage());
258
        } catch (KeyManagementException exp) {
259
            System.out.println("KeyManagementException exception occurred " + exp.getMessage());
260
        } catch (UnrecoverableKeyException exp) {
261
            System.out.println("UnrecoverableKeyException exception occurred " + exp.getMessage());
262
        }
263
 
          
264
        if ((sslContext == null)) {
265
            System.out.println("[initializeSSLContext] sslContext is null");
266
            System.out.println("[initializeSSLContext] verify ssl config");
267
            System.out.println("MyREST application exit with status code -1");
268
        }
269
        System.out.println("[initializeSSLContextP12Cert] Truststore and KeyStore initialized");
270
        return sslContext;
271
    }
272
 
          
273
}
274
 
          



I have designed the above class to support both PKCS12 (p12) and JKS keystore. Check variable keystoreType in the above codes, you need to assign keystoreType="PKCS12" for a PKCS12 (p12) certificate. But keep in mind that your trust store always should be in JKS format for this project. So create a truststore in JKS format.

Important point to note here is that you can see that I have commented out a portion of codes at the top. Normally when you create a key store you need to provide your server name when it prompted for “enter your first name and last name” and if that server name is not same as your REST service hostname you will get a certificate authentication error like localhost name/or your hostserver name not found (in this case hostname is localhost). In that case just uncomment that portion of code and modify localhost to our client server host name or just return always true for testing purpose.

Step 3: Now create a java class called CustomFeignConfiguration.java. This is our customized feign configuration class through which we will configure our keystore and truststore for an SSL Handshake to consume remote REST service through a secured channel.

Please modify it like below:

Java
 




xxxxxxxxxx
1
32


 
1
package com.example.bhaiti.assam.feignclientdemo;
2
 
          
3
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
5
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
6
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
7
import org.springframework.context.annotation.Bean;
8
import org.springframework.context.annotation.Configuration;
9
import feign.Client;
10
    
11
@Configuration
12
public class CustomFeignConfiguration {
13
 
          
14
    @Bean
15
    public Client getfeignClient()
16
    {
17
        Client trustSSLSockets = new Client.Default(
18
                SSLUtil.getClientSSLSocketFactory(),
19
                null);
20
 
          
21
        return trustSSLSockets;
22
    }
23
    
24
    @Bean
25
    @ConditionalOnMissingBean
26
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
27
            SpringClientFactory clientFactory) {
28
        return new LoadBalancerFeignClient(new Client.Default(null, null),
29
                cachingFactory, clientFactory);
30
    }
31
}
32
 
          



Step 4:  Now create your feign client like below. (You need to make changes as per your requirement this is just an example)

Java
 




xxxxxxxxxx
1
30


 
1
package com.example.bhaiti.assam.feignclientdemo;
2
 
          
3
import java.net.URI;
4
import java.util.Map;
5
 
          
6
import org.springframework.cloud.openfeign.FeignClient;
7
import org.springframework.http.HttpHeaders;
8
import org.springframework.http.ResponseEntity;
9
import org.springframework.web.bind.annotation.GetMapping;
10
import org.springframework.web.bind.annotation.PostMapping;
11
import org.springframework.web.bind.annotation.RequestBody;
12
import org.springframework.web.bind.annotation.RequestHeader;
13
import org.springframework.web.bind.annotation.RequestParam;
14
 
          
15
 
          
16
//Here we are informing FeignClient that we have a custom configuration class
17
//FeignConfiguration and requesting it to use that as configuration class 
18
@FeignClient(name = "sslClient", configuration = FeignConfiguration.class)
19
public interface GRClient{
20
    @GetMapping("/repository")
21
    ResponseEntity<?> getAllRepsitoryForASite(URI determinedBasePathUri,
22
                @RequestHeader HttpHeaders header, 
23
                @RequestParam(value="siteName",required = false) String siteName);
24
                
25
    @PostMapping(value = "/register", consumes = "application/json", produces = "application/json")
26
    public ResponseEntity<String> register(URI determinedBasePathUri, 
27
                        @RequestHeader("Authorization") String token,
28
                        @RequestBody Repository repo);      
29
}
30
 
          



In the above codes please notice, the first parameter of each method. Here we are passing the first parameter a URL of the remote REST service. Feign Client will expand it like determinedBasePathUri/retister (in our case it would be https:localhost:8443/register with a authorization header(token) for authentication (user id and password) and request body repo which is a object of type Repository.

Step 5: Finally create a controller class for this FeignClient for testing the above implementation:


Java
 




x
53


 
1
package com.example.bhaiti.assam.feignclientdemo;
2
 
          
3
import java.net.MalformedURLException;
4
import java.net.URI;
5
import java.net.URL;
6
 
          
7
import org.springframework.beans.factory.annotation.Autowired;
8
import org.springframework.http.ResponseEntity;
9
import org.springframework.stereotype.Controller;
10
import org.springframework.web.bind.annotation.GetMapping;
11
 
          
12
import com.netflix.appinfo.InstanceInfo;
13
import com.netflix.discovery.EurekaClient;
14
import com.netflix.discovery.shared.Application;
15
 
          
16
@Controller
17
public class GRController {
18
 
          
19
    @Autowired
20
    GRClient grClient;
21
    
22
   //if you are not using a EurekaClient remove below codes 
23
  //and return a hardcoded url from  getBaseUrl() instead.
24
    @Autowired
25
    private EurekaClient eurekaClient;
26
     
27
    public String getBaseUrl() {
28
        Application application = eurekaClient.getApplication("global-repository");
29
        InstanceInfo instanceInfo = application.getInstances().get(0);
30
        String hostname = instanceInfo.getHostName();
31
        int port = instanceInfo.getPort();
32
        return "https://" + hostname + ":" + port;
33
       
34
        //if you are not using a discovery server just return like below or as per your requirement
35
        //return "https://" + host + port;
36
    }
37
 
          
38
    @GetMapping("/repository")
39
    ResponseEntity<?> getAllRepsitoryForASite(@RequestHeader HttpHeaders header, @RequestParam(value="siteName",required = false) String siteName){
40
        URI determinedBasePathUri = URI.create(getBaseUrl());
41
        
42
        return grClient.getAllRepsitoryForASite(determinedBasePathUri,header, siteName);
43
    }       
44
    
45
    
46
    @PostMapping("/register")
47
    public ResponseEntity<String> register(@RequestHeader("Authorization") String token, @RequestBody Repository repo)  {
48
      //here you need to change Repository repo as per your requirement
49
        URI determinedBasePathUri = URI.create(getBaseUrl());
50
        return grClient.register(determinedBasePathUri,token,repo);
51
    }
52
    
53
}



Now when you make a REST service call to this service like http://localhost:8080/repository?siteName=Jorhat, inside REST api method getAllRepsitoryForASite it will call first getBaseUrl method and from getBaseUrl it will retrieve hostname and port detail of secured microservice global-repository which is registered in Discovery Server. Finally the URL has been passed as first parameter of method getAllRepsitoryForASite(determinedBasePathUri,header, siteName) of FeignClient. The Feign Client will use the certifiate that has been stored in keyStore to make a SSL based REST call to microservice called global-repository.

Hope you enjoyed this article. Making a secured call is not so easy. You should be very careful while creating your own keyStore, trustStore and client certificate etc. If you are doing a home work, while creating the certificate in the prompt “enter your first name and last name”, always provide localhost or your machine name. Never provide your name. To know more about SSL based communication you can refer to my article https://dzone.com/articles/ssl-based-https-restful-and-soap-client-applicatio

Topics:
feign client, microservice, microservice architecture, spring cloud, ssl, ssl certificate

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}