Using the DoubleIt web service as a starting point, this tutorial shows how to secure a Tomcat 8.5.x-hosted CXF web service and standalone SOAP client with transport layer security (TLS, aka SSL) and basic authentication. With TLS, the entire SOAP request and response is encrypted at the transport layer. See the security section of my blog index for alternative methods using message-layer encryption (such as UsernameToken or X.509 token profiles), where only portions of the SOAP envelope are encrypted during transmission.
The finished tutorial source code can be obtained from GitHub by using either the download ZIP button or git clone -v git://github.com/gmazza/blog-samples.git
command.
Activating SSL for a Web Service:
Create a server key for Tomcat if it does not already have one. The Tomcat SSL guide explains this process. For development mode only, you can use a self-signed certificate, in particular, if you are prototyping both the web service and client on the same machine, you can use Java keytool to create a server key with a commonName of "localhost":
keytool -genkeypair -keyalg RSA -validity 730 -alias myserverkey -keystore serverKeystore.jks -dname "cn=localhost" -keypass ???? -storepass ????
Note that Tomcat expects the values of the keypass
and storepass
passwords to be the same. Also for non-localhost testing, for the client to accept the service certificate the commonName field needs to match the URL domain name (e.g., "cn=www.myserver.com") where the service will be hosted.
The above keytool
command creates a serverKeystore.jks
keystore containing a new key for the server, valid for two years (730 days). I placed the file in Tomcat's conf folder but it can go elsewhere, you'll specify the location for Tomcat to find it in the next step.
Activate Tomcat's SSL port. Edit Tomcat's $CATALINA_HOME/
file to activate your preferred SSL connector and configure it to use your new keystore. For my sample I used:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" SSLEnabled="true"> <SSLHostConfig> <Certificate certificateKeystoreFile="conf/serverKeystore.jks" certificateKeystorePassword="**password here**" type="RSA" /> </SSLHostConfig> </Connector>
The Tomcat documentation has more information on editing this file. After configuring this information, restart Tomcat to load this new information.
Set server-side restrictions on the SSL cipher suites available for the SOAP calls (optional). The total set of cipher suites available is determined by the JSSE suite in use by the JRE being used by Tomcat. It can be further restricted however by either the web service provider or the SOAP client. On the service side, this process is container-dependent, with Tomcat having an SSL connector ciphers value that can be used to provide this information.
Update the web.xml to require SSL and basic authentication. We'll use the web.xml security-constraint
, login-config
and security-role
elements (tutorial) for this. Add the following configuration to the web.xml file listed in Step #4 of the DoubleIt tutorial:
<security-constraint> <web-resource-collection> <web-resource-name>restricted web services</web-resource-name> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <!-- role listed in security-role element below and in tomcat-users.xml file --> <role-name>mywsrole</role-name> </auth-constraint> <user-data-constraint> <!-- require SSL --> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> <!-- require basic authentication --> <login-config> <auth-method>BASIC</auth-method> </login-config> <security-role> <role-name>mywsrole</role-name> </security-role>
Add users and a new role to the application server. In the previous step we created a new role mywsrole
to designate the servlet container role that users must have in order to access the web service. We now need to add this role to the $CATALINA_HOME/
file and grant this role to new or existing users in that file:
<?xml version='1.0' encoding='utf-8'?> <tomcat-users> <role rolename="manager"/> <user username="tomcat" password="?????" roles="manager"/> <role rolename="mywsrole"/> <user username="alice" password="clarinet" roles="mywsrole"/> <user username="bob" password="trombone" roles="mywsrole"/> <user username="chuck" password="harmonica" roles="mywsrole"/> </tomcat-users>
Update the endpoint address in the WSDL service section to use the https protocol and port. This is the WSDL in Step #3 of the DoubleIt tutorial. The new SOAP address to use:
<wsdl:service name="DoubleItService"> <wsdl:port name="DoubleItPort" binding="tns:DoubleItBinding"> <soap:address location="https://localhost:8443/doubleit/services/doubleit"/> </wsdl:port> </wsdl:service>
Redeploy the web service. Follow Step #7 of the DoubleIt tutorial. We'll see that the SSL-enabled web service is working properly once we configure the SOAP client below. At this stage you might wish to confirm you can see the WSDL from a browser at https://localhost:8443/
Activating SSL for the SOAP Client:
Import the server's public key into the web service client's truststore. The truststore can be either the cacerts file of the JRE that the SOAP client is using (which would result in all Java applications using that JRE to trust the server hosting the web service) or a SOAP-client specific truststore. Either way, export the certificate from the keystore you created above into a file:
keytool -export -rfc -keystore serverKeystore.jks -alias myserverkey -file MyServer.cer
Then import the certificate into the SOAP client's truststore. For the simpler case of relying on the JRE's default truststore ($JAVA_HOME/jre/lib/security/cacerts, with default password "changeit"), first (optionally) back up the cacerts file and then run:
keytool -import -noprompt -trustcacerts -alias myserverkey -file MyServer.cer -keystore $JAVA_HOME/jre/lib/security/cacerts
Alternatively, if you wish to use a client-specific truststore:
Import the server certificate into a new (or already existing) truststore:
keytool -import -noprompt -trustcacerts -alias myserverkey -file MyServer.cer -keystore clienttruststore.jks -storepass ????
If the given clienttruststore.jks file doesn't exist, Keytool will first create it with the password provided by the -storepass
value. If the truststore already exists, the -storepass
value will need to be the correct for the truststore.
Link the client-specific truststore to the SOAP client as shown here. Place this configuration in a cxf.xml file in a new src/main/
Note that after you are finished with this tutorial, you can remove this certificate from the cacerts (or client-specific) truststore by using a command similar to the below:
keytool -delete -alias myserverkey -keystore $JAVA_HOME/jre/lib/security/cacerts
Set client-side restrictions on the SSL cipher suites available for the SOAP calls (optional). This can be accomplished in CXF by use of the cipherSuitesFilter element in the cxf.xml configuration file (see also here).
Update the SOAP client to provide the basic auth username and password. CXF allows for setting the BindingProvider's username and password properties to accomplish this. This information can also be placed in cxf.xml configuration files using the http:authorization element. The updated DoubleIt SOAP client below uses the former method:
Running the client as shown in Step #9 of the tutorial should give the desired output. After confirming this, also test with incorrect usernames and/or passwords to make sure that the basic authentication checks are working. Note if you're using a cxf.xml configuration file, you'll need to include the spring-context dependency in the client pom.xml or the configuration file will not be read.
Troubleshooting:
If the client's keystore does not have the server's public key configured, you may see this message while running the client:
WARNING: Interceptor for {http://www.example.org/contract/DoubleIt}DoubleItService#{http://www.example.org/contract/DoubleIt}DoubleIt has thrown exception, unwinding now org.apache.cxf.interceptor.Fault: Could not send Message. at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:64) at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) ... at client.WSClient.doubleIt(WSClient.java:30) at client.WSClient.main(WSClient.java:23) Caused by: javax.net.ssl.SSLHandshakeException: SSLHandshakeException invoking https://localhost:8443/doubleit/services/doubleit: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
If the username and password supplied by the client were inaccurate, or if CXF failed to detect any credentials being provided:
... at client.WSClient.doubleIt(WSClient.java:30) at client.WSClient.main(WSClient.java:23) Caused by: org.apache.cxf.transport.http.HTTPException: HTTP response '401: null' when communicating with https://localhost:8443/doubleit/services/doubleit
Additional Notes:
The default endpoint URL that the SOAP client uses is defined in the WSDL whose location is hardcoded in the Service subclass (DoubleItService here.) This can be overridden in the SOAP client by setting the ENDPOINT_ADDRESS_PROPERTY. In the SOAP client above I added a check to make sure SSL is being used prior to the SOAP call being made. This helps protect against sensitive data in the SOAP body being sent out unencrypted in case a non-https:// URL was accidentally configured.
The sample Tomcat server key created in this tutorial used "localhost" as its distinguished name (dname) so that clients having the server's public key in its truststore could access the service using an https://localhost:...
endpoint URL. If the server key however has an actual dname (e.g., acme.com
) but you are still accessing it via localhost
while doing development, you can temporarily have the SOAP client ignore the different dname -- see CXF's disableCNCheck
setting.
See the CXF wsdl_first_https sample (available in the CXF download) to learn how to set up two-way SSL, where the service must also confirm a client certificate for the SOAP call to proceed.
Posted by Glen Mazza in Web Services at 07:00AM Mar 26, 2017 | Comments[1]
First off, thank you for these excellent tutorials. You've been a real life saver!!
I'm kind of a newb and completed the prior tutorial using the tomcat7-maven-plugin route. This tutorial seems to me (I could be wrong) to assume you're doing a stand along tomcat setup for step 1 and 2 (storing the key in the tomcat conf folder, and updating the web xml).
Was able to get the server running on port 8443 using the tomcat7-maven-plugin route by using the following xml in my pom.xml.
Sharing in case it'd be useful for others :)
org.apache.tomcat.maven tomcat7-maven-plugin 8443 ${basedir}/src/main/resources/tomcat/serverKeystore.jks password
Posted by Ralph Callaway at 01:32AM Dec 07, 2018