WS Security Implementation Using Apache CXF and WSS4J
WS Security Implementation Using Apache CXF and WSS4J
WS Security Implementation Using Apache CXF and WSS4J
Contents
There are many web service frameworks available today and most of them support WS
Security. In this tutorial we are going to use Apache CXF framework with Spring
Framework. As we are going to focus mostly on security I am not going to explain in detail
how a CXF-Spring web service and client are written. Web Service itself is a huge topic! But
we do need to write a simple service and a client to explain the WS Security stuff. I would
recommend you to learn basics of Spring and Apache CXF framework and understand how
they integrate with each other and understand the configuration files. For this tutorial purpose
I have written a simple hello world service and a client. Please go through the page Web
Service and Client Using Apache CXF and get your setup ready to dive in further!
CXF implements WS-Security by integrating WSS4J. Apache Axis framework also uses
WSS4J. The Apache WSS4J project provides a Java implementation of the primary security
standards for Web Services, namely the OASIS Web Services Security (WS-Security)
specifications. Through a number of standards such as XML-Encryption, XML-Signature
and headers defined in the WS-Security standard, it allows you to:
There are two ways CXF allows us to configure WS Security using WSS4J.
In part 6 of this tutorial we shall talk about WS SecurityPolicy and show how in CXF that is
implemented. Here we are going to work with standard WSS4J interceptors in CXF. To
configure WSS4J with CXF we basically need to follow four steps assuming you have got all
the dependent libraries in your projects in both client and server side.
1. Get your client and server certificates, keystore and truststore ready. Make them
available in classpath.
2. Write a property file to point to keystore and truststore properties (location, password,
alias etc) to feed them to WSS4J. These properties are called crypto properties.
3. Write a password callback handler class to tell WSS4J about the private key password
in the key store.
4. Write inbound and outbound interceptor beans. This beans will have a bunch of key
value pairs in a map to define the characteristics of the security mechanism.
The crypto properties that we need to provide in a property file, in step 2, and key/value pairs
that we need to define in inbound and outbound interceptor beans to characterize the security
mechanism, in step 4, are defined at WSS4J configuration page. It is worth looking at this
page. As you have already understood the web security specification now you would be able
to relate key/value pairs, also called WSHandler configuration tags, mentioned in the page.
As you may have guessed that after creating a basic service and client we need digital
certificates to start implementing WS Security. This section prepares required server and
client side digital certificates.
We have already learnt how to create certificates using Java Keytool and get it signed by a
CA using OenSSL. We are going to repeat that here again. Under C drive create a folder
C:\>WSST\KeyStores. I have put the commands at server key store page in order to avoid
this page getting unnecessary lengthy. Follow the steps as mentioned in the page and you
should be done with server ceritficates and keystore. Now copy the server key store in
HelloWorldService project's config directory.
C:\WSST\KeyStores>copy ServerKeyStore.jks
..\HelloWorldService\config\ServerKeyStore.jks
1 file(s) copied.
Like server key store we need to prepare the client key store as well. Check client key store
page for the commands. Copy the ClientKeyStore.jks in HelloWorldClient's config directory.
C:\WSST\KeyStores>copy ClientKeyStore.jks
..\HelloWorldClient\config\ClientKeyStore.jks
1 file(s) copied.
WSS4J needs to be feed with some crypto properties. These properties are grouped in three
sections as shown below
#Crypto properties
#General properties:
#Keystore properties:
#TrustStore properties:
Some of the properties have default values which is fine with us. For the general properties
we are fine with the default values, for the keystore properties we need some of them. But in
this demo we do not need any truststore. You may be wondering, why? Well, in simple
cases, like this demo, we have only one certificate to trust i.e. Test CA's root certificate
which has already been imported in the <Client/Server>KeyStore.jks and which is going to
be pointed as keystore file that is why we do not need a separate truststore. But in cases
where client's certificate is signed by a different CA and server does not want to import the
same in its own keystore (which is recommended) then we need a separate truststore where
client's signer's root CA need to be imported, otherwise server would not trust the client
certificate. The same is also applicable when Client needs to trust a Server certificate which
is signed by a different CA other than the Client's CA.
So in our case we only need the properties marked in RED color. With those properties let us
create to crypto files one for server and one for client.
server-crypto.properties
org.apache.ws.security.crypto.merlin.keystore.file=ServerKeyStore.jks
org.apache.ws.security.crypto.merlin.keystore.password=server-pass
org.apache.ws.security.crypto.merlin.keystore.alias=server
client-crypto.properties
org.apache.ws.security.crypto.merlin.keystore.file=ClientKeyStore.jks
org.apache.ws.security.crypto.merlin.keystore.password=client-pass
org.apache.ws.security.crypto.merlin.keystore.alias=client
But traditional practice is to provide through a password callback handler to make it more
secure. Hmm it is private key password!! Being a Java class, the password callback handler
can fetch the password from a Database, LDAP or from more secured places. In this case we
would just echo the private key password from the password callback handler class. Below
are our password callback handlers.
package server;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
if (pc.getUsage() == WSPasswordCallback.SIGNATURE
|| pc.getUsage() == WSPasswordCallback.DECRYPT)
if (pc.getIdentifier().equals("server"))
pc.setPassword("key-pass");
}
}
}
package client;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
if (pc.getUsage() == WSPasswordCallback.SIGNATURE
|| pc.getUsage() == WSPasswordCallback.DECRYPT)
if (pc.getIdentifier().equals("client"))
pc.setPassword("key-pass");
}
}
}
The final step is to write the inbound and outbound WSS4J interceptors for both client and
server/service side. In total we have to write four interceptors. The interceptor beans
configure the rest of the security characteristics like, whether the SOAP message will contain
timestamp, username token, signature, encryption etc or not. What parts need to be signed or
encrypted, what algorithm to use for different purposes, what type of BinarySecurityToken
and reference would be used, pointer to Password Callback Handler class etc. All these
settings are configured as Key/Value pairs in a map. The whole set is listed as WSHandler
Tags in WSS4J configuration page. Please note many of the keys have default values.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="logInBound"
class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="logOutBound"
class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<jaxws:client id="helloClient"
serviceClass="com.ddmwsst.helloworld.HelloWorld"
address="http://localhost:8080/helloworld/HelloWorld">
<jaxws:inInterceptors>
<ref bean="logInBound" />
<ref bean="inbound-security" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<ref bean="logOutBound" />
<ref bean="outbound-security" />
</jaxws:outInterceptors>
</jaxws:client>
</beans>
Explanation:
Inbound is really simple, it just says that in the SOAP there should be a timestamp, signature
and encryption. In-bound is not expecting that Part X must be signed or Part Y must be
encrypted. Parts can be anything just that there should a signature element and an ecrypted
element at the least. You may be wondering how inbound encrypted SOAP envelop getting
parsed. But remember when we learnt about a security enabled SOAP envelop we told that
instructions to process the message are embedded in the message itself.
Here is the server/service side inbound/outbound interceptors. Again pay attention to the
inbound-security and outbound-security interceptors.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="logInBound"
class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="logOutBound"
class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<jaxws:endpoint id="helloWorld"
implementor="server.HelloWorldImpl" address="/HelloWorld">
<jaxws:inInterceptors>
<ref bean="logInBound" />
<ref bean="inbound-security" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<ref bean="logOutBound" />
<ref bean="outbound-security" />
</jaxws:outInterceptors>
</jaxws:endpoint>
</beans>
Explanation:
It says useReqSigCert. You would have expected it to be client, right? After all, when
server sends an encrypted message it should encrypt the message using clients public key.
This is a special value in WSS4J it means use Request Signing Certificate. It allows any
client having server's public key to send a message to the service/server. Server need not
have client's public key in its keystores but would expect client's public key in the SOAP
message itself. This is why we had used <entry key="signatureKeyIdentifier"
value="DirectReference"/> in client's outbound-security interceptor to embed the
actual certificate. If we had omitted this WSS4J by default would have used IssuerSerial as
signatureKeyIdentifier and in that case only Issuer name and serial number would come in
the BinarySecurityToken and not the client's public key. And server would not find it and fail
to verify the client's signature breaking the communication path.
One more point to note here is that server outbound-security does not specify any key-
identifier, neither for signature nor for encryption. So WSS4J would use the default key-
identifier mechanism which is IssuerSerial. So for the signature server's outbound message
will contain server certificate's issuer name and the serial number and nothing else. As client
has the server certificate in its key-store it is not going to be a problem. And for the
encryption also server is going to specify client's certificate's issuer name and serial number
only. Again it will not be an issue for client as client knows its own issuer and
serial. Theoretically you can include server's and client's public key in the SOAP using e.g.
DirectReference but it is unnecessary here.
This step is simple and applicable only for the service side for this demo purpose. In the
provided build.xml in the HelloWorldService project there is a target createWar. Please run
that target and a war file should be generated. If you closely look at the ant target it makes
sure that all the files in config directories are copied to WEB-INF/classes directory to make
them available in the application classpath. Deploy the war n your Tomcat and makes sure it
starts up without any error.
Once the service side or the server is ready let us send a secure request to the server. To do
that we just need to run the client making sure crypto properties file and keystores are in
classpath. I have provided a build.xml in the client project to a test client. Execute the
runMain ant target and it should send a security enabled SOAP message to server. Server
also should respond with a SOAP message as per security configuration. A sample request
and response message are attached herewith for your inspection.
I have also attached the demo service and client projects at the attachment section.
Further Reading