Using the DoubleIt web service as a starting point, this tutorial shows how to secure a Tomcat-hosted web service and independent SOAP client with transport layer security (TLS, aka SSL) and basic authentication. Both CXF and Metro are covered with the instructions below. With TLS, the entire SOAP request and response is encrypted at the transport layer. For an alternative method using message-layer encryption (such as with the OASIS UsernameToken, X.509 and/or SAML token profiles), see the security section of my blog article list.
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 as explained in Michael Remijan's article--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 common name of "localhost":
keytool -genkey -alias myserverkey -keyalg RSA -sigalg SHA1withRSA -keypass ???? -storepass ???? -keystore serverKeystore.jks -dname "cn=localhost"
(The purpose of the
-dname
option is explained in Remijan's article.) This will create aserverKeystore.jks
keystore containing a new key for the server. For convenience, you may wish to place this file in Tomcat's base ($CATALINA_HOME) folder. Note that Tomcat expects the values of thekeypass
andstorepass
passwords to be the same.Activate Tomcat's SSL port. Edit Tomcat's
$CATALINA_HOME/conf/server.xml
file to activate (uncomment) the SSL connector running on port 8443 by default and configure it to use your new keystore:<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" keystoreFile="serverKeystore.jks" keystorePass="?????" clientAuth="false" sslProtocol="TLS" />
The Tomcat documentation has more information on editing this file. The
keystoreFile
attribute above is assuming the keystore is located in $CATALINA_HOME, and be sure to updatekeystorePass
with the actual (common key and keystore) password. After configuring this information, shut down and restart Tomcat to load this new information.Set server-side restrictions on the SSL cipher suites available for the SOAP calls (optional). Although not being done for the purposes of this tutorial, the following notes provide some information on cipher suite filtering should you wish to restrict acceptable cipher suites for the SOAP calls. The total set of cipher suites available are determined by the JSSE suite in use by the JRE being used by the web service. They can be further restricted however by either the web service provider or the SOAP client. On the service side, this process appears to be container-dependent, such as by setting the Tomcat SSL connector ciphers value for Tomcat-hosted web services. For Endpoint-hosted web services, setting the JSSE properties for Metro or the Jetty properties for CXF (which uses Jetty to implement Endpoint) should also work.
Update the web.xml to require SSL and basic authentication. We'll use the web.xml security-constraint and login-config elements for this. Add the following section to the web service's web.xml file listed in Step #6 of the DoubleIt tutorial. Anywhere within the
web-app
element is acceptable--ordering of the web.xml elements is not strict, as shown by theweb-appType
complexType in the web.xml XSD.<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 users to the$CATALINA_HOME/conf/tomcat-users.xml
file and give them this role (or just grant the role to already existing users):<?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 #4 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 #8 of the DoubleIt tutorial. We'll see the SSL-enabled web service working once we configure the SOAP client below.
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"):
keytool -import -noprompt -trustcacerts -alias myserverkey -file MyServer.cer -keystore $JAVA_HOME/jre/lib/security/cacerts
Before running this command you might wish to back up the cacerts file. Note that after you are finished with this tutorial, you can remove this certificate from the cacerts truststore by using the following command:
keytool -delete -alias myserverkey -keystore $JAVA_HOME/jre/lib/security/cacerts
Alternatively, for a client-specific truststore, first 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 keystore password for it. Once done, link the client-specific truststore to the SOAP client as shown here: (CXF) (Metro).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. For Metro, setting the https.cipherSuites system property is probably the simplest option. Alternatively, Metro clients can override the SSLSocketFactory being used and place restrictions on the cipher suites available via this class'
getDefaultCipherSuites()
method.Update the SOAP client to provide the basic auth username and password. Both CXF and Metro allow for setting the BindingProvider's username and password properties to accomplish this. CXF users can also place this information in their cxf.xml configuration files using the http:authorization element. The updated DoubleIt SOAP client below uses the former method:
package client; import org.example.contract.doubleit.DoubleItPortType; import org.example.contract.doubleit.DoubleItService; import javax.xml.ws.BindingProvider; public class WSClient { public static void main (String[] args) { DoubleItService service = new DoubleItService(); DoubleItPortType port = service.getDoubleItPort(); BindingProvider portBP = (BindingProvider) port; String urlUsed = (String) portBP.getRequestContext(). get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY); System.out.println("Using URL: " + urlUsed); if (!"https://".equals(urlUsed.substring(0, 8))) { throw new IllegalStateException("Endpoint URL must use HTTPS!"); } portBP.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "bob"); portBP.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "trombone"); doubleIt(port, 10); doubleIt(port, 0); doubleIt(port, -10); } public static void doubleIt(DoubleItPortType port, int numToDouble) { int resp = port.doubleIt(numToDouble); System.out.println("The number " + numToDouble + " doubled is " + resp); } }
Running the client as shown in Step #10 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.
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 using the ENDPOINT_ADDRESS_PROPERTY as shown in the SOAP client tutorial. 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 vialocalhost
while doing development, you can temporarily have the SOAP client ignore the dname when validating the server. Metro has the option to set a HostnameVerifier that will accept any dname, while CXF has adisableCNCheck
setting that will accomplish the same.For a Metro SOAP client, you may get an HTTP 401 authentication failed error message when the DoubleItService object is created in the WSClient sample above. That is because Metro runtime in certain cases goes over the network to retrieve the latest WSDL and the basic authentication username and password added to the SOAP client works only with the subsequent SOAP call, not the initial WSDL retrieval. Jitu of the Metro team lists three possible workarounds to this problem: removing the authentication requirements for GET from the web.xml (i.e., allowing anyone to see the WSDL), catalog files to locate the WSDL, or subclassing the Java Authenticator class.
See the FUSE Services Framework Security Guide for more information on configuring SSL for CXF. Metro-specific information can be found in its User's Guide.
See my blog index for more web service related articles.