Вы находитесь на странице: 1из 11

WS Security Implementation Using Apache CXF and WSS4J

Contents

1. 1 Create A Web Service And Client using Apache CXF


2. 2 A Quick Tour of WSS4J
3. 3 Preparing Keystores and Certificates
1. 3.1 Server Certificates and KeyStore
2. 3.2 Client Certificates and KeyStore
4. 4 Create Crypto Properties File
5. 5 Write Password Callback Handler
6. 6 Write WSS4J Interceptors
7. 7 Deploy Web Service
8. 8 Test The Service
9. 9 Further Reading

Create A Web Service And Client using Apache CXF

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!

A Quick Tour of WSS4J

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:

 Pass authentication tokens between services


 Sign messages
 Encrypt messages or parts of messages
 Timestamp messages
In this page we are not going to show Username Token, i.e. pass authentication token in
SOAP message. But we shall see the rest i.e. signing, encrypting and time-
stamping messages. Username token will be covered at WS Security With
UsernameToken page. Normally username token is not used when client signs a message
using a digital certificate. Digital certificate itself is the identity of the client.

There are two ways CXF allows us to configure WS Security using WSS4J.

 Using standard WSS4J interceptors


 Using WS SecurityPolicy Framework

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.

Preparing Keystores and Certificates

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.

Server Certificates and KeyStore

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.

Client Certificates and KeyStore

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.

Create Crypto Properties File

WSS4J needs to be feed with some crypto properties. These properties are grouped in three
sections as shown below

#Crypto properties

#General properties:

#WSS4J specific provider used to create Crypto instances. Defaults to


"org.apache.ws.security.components.crypto.Merlin".
#org.apache.ws.security.crypto.provider=

#The provider used to load keystores. Defaults to installed provider.


#org.apache.ws.security.crypto.merlin.keystore.provider=

#The provider used to load certificates. Defaults to keystore provider.


#org.apache.ws.security.crypto.merlin.cert.provider=

#The location of an (X509) CRL file to use.


#org.apache.ws.security.crypto.merlin.x509crl.file=

#Keystore properties:

#The location of the keystore


org.apache.ws.security.crypto.merlin.keystore.file=

#The password used to load the keystore. Default value is "security".


org.apache.ws.security.crypto.merlin.keystore.password=

#Type of keystore. Defaults to: java.security.KeyStore.getDefaultType()),


normally it is JKS
#org.apache.ws.security.crypto.merlin.keystore.type=

#The default keystore alias to use, if none is specified.


org.apache.ws.security.crypto.merlin.keystore.alias=
#The default password used to load the private key. Normally it is
provided through a password-callback class
#org.apache.ws.security.crypto.merlin.keystore.private.password=

#TrustStore properties:

#Whether or not to load the CA certs in ${java.home}/lib/security/cacerts


(default is false)
#org.apache.ws.security.crypto.merlin.load.cacerts=

#The location of the truststore


#org.apache.ws.security.crypto.merlin.truststore.file=

#The truststore password. Defaults to "changeit".


#org.apache.ws.security.crypto.merlin.truststore.password=

#The truststore type. Defaults to:


java.security.KeyStore.getDefaultType().
#org.apache.ws.security.crypto.merlin.truststore.type=

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

Copy this properties file to c:\>WSST\HelloWorldService\config\server-


crypto.properties

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

Copy this properties file to c:\>WSST\HelloWorldClient\config\client-


crypto.properties

Write Password Callback Handler


Next we need to write password callback handlers for both client and server. So why do you
need a password callback handler? You probably have noticed while creating the keystores,
we have supplied two passwords, one for the keystore itself and the other to preserve the
private key. The keystore password is specified in the crypto properties file as shown in the
previous section. But we have not supplied yet the private key password. In crypto properties
file there is a property to specify this i.e.

#The default password used to load the private key. Normally it is


provided through a password-callback class
#org.apache.ws.security.crypto.merlin.keystore.private.password=

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.

Server Private Key Password Callback Handler:

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;

public class ServerPasswordCallback implements CallbackHandler {

public void handle(Callback[] callbacks) throws IOException,


UnsupportedCallbackException {

for (int i = 0; i < callbacks.length; i++) {

WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];

if (pc.getUsage() == WSPasswordCallback.SIGNATURE
|| pc.getUsage() == WSPasswordCallback.DECRYPT)

if (pc.getIdentifier().equals("server"))
pc.setPassword("key-pass");
}

}
}

Client Private Key Password Callback Handler:

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;

public class ClientPasswordCallback implements CallbackHandler {

public void handle(Callback[] callbacks) throws IOException,


UnsupportedCallbackException {

for (int i = 0; i < callbacks.length; i++) {

WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];

if (pc.getUsage() == WSPasswordCallback.SIGNATURE
|| pc.getUsage() == WSPasswordCallback.DECRYPT)

if (pc.getIdentifier().equals("client"))
pc.setPassword("key-pass");
}

}
}

Write WSS4J Interceptors

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.

Here is the client outbound/inbound WSS4J interceptors. Pay attention to outbound-security


and inbound-security beans.

<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>

<!-- WSS4JOutInterceptor for signing and encrypting outbound SOAP -->


<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"
id="outbound-security">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature
Encrypt"/>
<entry key="user" value="client"/>
<entry key="signaturePropFile" value="client-
crypto.properties"/>
<entry key="encryptionPropFile" value="client-
crypto.properties"/>
<entry key="signatureKeyIdentifier"
value="DirectReference"/>
<entry key="encryptionUser" value="server"/>
<entry key="passwordCallbackClass"
value="client.ClientPasswordCallback"/>
<entry key="signatureParts"
value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-utility-
1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body
"/>
<entry key="encryptionParts"
value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{ht
tp://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionSymAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</map>
</constructor-arg>
</bean>

<!-- WSS4JInInterceptor for decrypting and validating the signature


of inbound SOAP -->
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"
id="inbound-security">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="signaturePropFile" value="client-
crypto.properties"/>
<entry key="decryptionPropFile" value="client-
crypto.properties"/>
<entry key="passwordCallbackClass"
value="client.ClientPasswordCallback"/>
</map>
</constructor-arg>
</bean>

</beans>
Explanation:

If you look at the outbound-security out-interceptor, it takes map as a constructor argument.


And the map has a bunch of key value pairs. Most of them are self explanatory.
The action key has a value of "Timestamp Signature Encrypt" . This is how
WSS4J is advised to put a timestamp in the SOAP message, and there is signing and
encryption.
The signatureKeyIdentifier says how the security token used in signing (here the
client's public key) should be referenced. In this case it is a direct reference. If this is not
specified, WSS4J uses a default as per X.509 certificate profile specification, which is
IssuerSerial. Where it will just specify the issuer name of the certificate and the serial
number and not the actual certificate. Since we have not imported client's public key in
server's key store so we do need to send the whole certificate in the SOAP message itself.
The key encryptionUser says that it would encrypt the message using a public key whose
alias is server. Remember, we did import server's public key in client's key store at
the beginning.
signatureParts and encryptionParts points to which parts need signature and encryption
respectively. Please note that parts are specified as ';' separated and each part is defined as
{Element|Content}{Fully qualified namespace} Part-Name.

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">

<import resource="classpath:META-INF/cxf/cxf.xml" />


<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<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>

<!-- WSS4JInInterceptor for decrypting and validating the signature of


inbound SOAP -->
<bean id="inbound-security"
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="signaturePropFile" value="server-
crypto.properties"/>
<entry key="decryptionPropFile" value="server-
crypto.properties"/>
<entry key="passwordCallbackClass"
value="server.ServerPasswordCallback"/>
</map>
</constructor-arg>
</bean>

<!-- WSS4JOutInterceptor for signing and encrypting outbound SOAP --


>
<bean id="outbound-security"
class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature
Encrypt"/>
<entry key="user" value="server"/>
<entry key="signaturePropFile" value="server-
crypto.properties"/>
<entry key="encryptionPropFile" value="server-
crypto.properties"/>
<entry key="encryptionUser" value="useReqSigCert"/>
<entry key="passwordCallbackClass"
value="server.ServerPasswordCallback"/>
<entry key="signatureParts"
value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-utility-
1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body
"/>
<entry key="encryptionParts"
value="{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionSymAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</map>
</constructor-arg>
</bean>

</beans>

Explanation:

The inbound-security WSS4J interceptor is similar to client's inbound-security interceptor.


Thanks to secured SOAP messages' self-instructed characteristic. The SOAP message can
itself tell how it needs to be parsed/processed.
And most of the keys for outbound-security are also similar to client's outbound-security. But
the following key-value looks odd right?

<entry key="encryptionUser" value="useReqSigCert"/>

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.

Deploy Web Service

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.

Test The Service

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

Web Service Security For Java


Understanding Password Callback Handlers