SSL / TLS REST Server – Client with Spring and TomEE

posted by Roberto Cortez on

When building a system, developers usually disregard the security aspects. Security has been always something very important to worry about, but it’s attracting even higher concerns than before. Just this year we had a few cases like the Heartbleed Bug or the CelebrityGate scandal. This has nothing to do with the post, but are just examples that security really matters and we should be aware of it.

With the increasing popularity of REST services it makes sense that these need to be secured in some way. A couple of weeks ago, I had to integrate my client with a REST service behind https. I have never done it before and that’s the reason for this post. I have to confess that I’m no security expert myself, so please correct me if I write anything stupid.

The Setup

For this example I have used the following setup:

I’m not going into many details about SSL and TSL, so please check here for additional content. Note that TLS is the new name for SSL evolution. Sometimes there is confusion between the two and people often say SSL, but use the newest version of TSL. Keep that in mind.

Don’t forget to follow the instructions on the following page to setup SSL for Tomcat: SSL Configuration HOW-TO. This is needed for the server to present the client with a set of credentials, a Certificate, to secure the connection between server and client.

The Code

Service

Let’s create a simple Spring REST Service:

RestService.java

And we also need some wiring for this to work:

RestConfig.java

web.xml

Please, note the elements security-constraint, user-data-constraint and <transport-guarantee>CONFIDENTIAL</transport-guarantee>. These are needed to specify that the application requires a secure connection. Check Securing Web Applications for Java Applications.

Running the Service

Just deploy the application on the TomEE Server using your favourite IDE environment and access https://localhost:8443/. You should get the following (you might need to accept the server certificate first):

Rest Service Localhost

Note that the browser protocol is https and the port is 8443 (assuming that you kept the default settings in SSL Configuration HOW-TO.

Client

Now, if you try to call this REST service with a Java client, most likely you are going to get the following message and Exception (or similar):

Message: I/O error on GET request for “https://localhost:8443/”:sun.security.validator.ValidatorException:

Exception: Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

This happens because the running JDK does not have a valid certificate for your server. You can import it, and get rid of the problem, but let’s do something more interesting. We are going to programatically supply a trusted keystore with our server certificate.

This is especially useful if:

  • you are running your code into multiple environments
  • you don’t have to manually import the certificate into the JDK every time
  • if you upgrade the JDK you have to remember about the certificates
  • for some odd reason you don’t have access to the JDK itself to import the certificate

Let’s write some code:

RestClientConfig.java

Here we use Spring RestOperations interface which specified a basic set of RESTful operations. Next we use Apache HTTP Components SSLConnectionSocketFactory which gives us the ability to validate the identity of the server against a list of trusted certificates. The certificate is loaded from the same file used on the server by KeyStore.

RestServiceClientIT.java

A simple test class. We also need a properties file with the keystore file location and password:

config.properties

This should work fine if you used all the defaults.

Running the Test

If you now run the test which invokes the REST service within a Java client, you should get the following output:

Response: <200 OK,Called the get Rest Service,{Server=[Apache-Coyote/1.1], Cache-Control=[private], Expires=[Thu, 01 Jan 1970 01:00:00 WET], Content-Type=[text/plain;charset=ISO-8859-1], Content-Length=[27], Date=[Tue, 23 Dec 2014 01:29:20 GMT]}>

Body: Called the get Rest Service

Conclusion

That’s it! You can now call your REST service with your client in a secured manner. If you prefer to add the certificate to the JDK keystore, please check this post.

Stay tuned for an equivalent for Java EE JAX-RS equivalent.

Resources

You can clone a full working copy from my github repository: REST SSL.

Comments ( 20 )

  1. ReplyAlex

    Hi Roberto, one question are you using a self generated PKCS12 key? For testing purposes I am using one created locally and it works, but browser always send me a warning message about the certificate is untrusted. Although I have tried to register inside browser, the same error is shown because there is no valid certificate chain. Do you know how to resolve these issues?

    • ReplyRoberto Cortez

      Hi Alex, Thank you for stopping by 🙂

      Yeah, I’m using a self generated key. I’m no expert on this, but you get the browser warning because the certificate is self-signed. One obvious way to fix this is to actually have a valid issuer to generate the certificate. But on the browser, after you accept the certificate it should ask about it again. Is it asking every time?

  2. Replyrmpestano

    Hi Roberto, nice article.
    One advantage to install the certificate on the JVM is that its easy to upgrate the clients when it expires or change. Ex: an app server with multiple apps, instead of upgrade each app you just update the server.

    • ReplyRoberto Cortez

      Hi rmpestano,

      Thank you for your feedback. You are right, I didn’t thought about that situation 🙂

  3. Replybrgrm

    @Alex you can (and should) get around the certificate warnings by creating your own local certificate authority and import the CA certificate in your browser. Google for it, it’s not that complicated.

    @Roberto: Why would you run Spring on TomEE instead of Tomcat? You can do the same thing with Java EE on TomEE or with Spring on Tomcat. Further for a proper security configuration you should have a user authentication on your rest service. Use @SecurityDomain and @RolesAllowed annotations

    • ReplyRoberto Cortez

      Hi brgrm,

      I’ve used TomEE because originally I was using Java EE. Later, because of client technologies I had to change it to Spring, but I still intent to finish up the sample in Java EE in a future post.

      About security, yeah I’ve should have used those annotation, but I was more focused in the client call with a certificate.

      Thank you

  4. ReplyMark

    What is BROWSER_COMPATIBLE_HOSTNAME_VERIFIER in RestClientConfig?

    It isn’t defined anywhere in your code.

  5. ReplyVadivel PM

    Hi, It is very nice article. It would be great ,Can any one share that complete source code to my mail-id vayalaivadivel@gmail.com.

  6. ReplyWh1t3

    Hello everyone
    I’m tired and i can’t find an example that can help me.Does the client in this example have its own certificate to be validated by the server?
    I’ve to establish a TLS connection with my server and i don’t know how to use my certificates into the request while maybe i’ve understood how to validate server’s certificate. Tks everyone!

    • ReplyRoberto Cortez

      Hi Wh1t3,

      Thank you for your question.

      Is just a self signed certificate. You usually add it when you set up an https connector. What is the exact problem that you are experiencing?

      Cheers,
      Roberto

  7. ReplyLemos

    Boa noite,
    Se por acaso eu quiser ter uma ligação constante com um serviço de rest que usa jaas como método de autenticação, uma vez que sempre que se autêntica demora 400ms, como poderia fazer tal coisa?

    Muito obrigado.
    Lemos

    • ReplyRoberto Cortez

      Viva Lemos,

      Tipicamente, apenas autenticas uma vez num serviço que te devolve um token de autenticação e nas chamadas seguintes utilizas esse token para autorizar o utilizador. Assim, não tens que estar sempre a passar por uma autenticação LDAP ou manter uma ligação constante.

      Roberto

  8. ReplyAnkit8051

    Hi Roberto,

    Thanks for the article. This article has resolved my issue while accessing https endpoint.

  9. Replypratap

    Hi Roberto,

    How to use TLS1.2 with JDK 1.5, please advise me.

    Thanks

    • ReplyRoberto Cortez

      Hi Pratap,

      Unfortunately, TLS 1.2 was only introduced in Java 7, so you cannot use it with 1.5. You either need to upgrade your Java version or maybe write your own: http://stackoverflow.com/questions/33364100/how-to-use-tls-1-2-in-java-6

      I would advice to upgrade the version of Java, since 1.5 is not supported anymore. Or alternative, I’m guessing that you are using some kind to application server where you need to run with TLS 1.2. You could frontend those servers with another server that supports it and then proxy the requests between servers.

      Cheers,
      Roberto

  10. ReplyAzar

    Hi.You have placed the config.properties in client app.I think probably you are testing the client on the machine on which server is running.So client app can find keystore file.If I have a remote client,how can I have a keystore file.Please throw some light,and correct me if I understood wrong.

    • ReplyRoberto Cortez

      It should be no different. You have a file for it and place it in the client or server.

Leave a Reply to pratap Cancel reply

Your email address will not be published.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>