This guide will lead you through how to configure a client and service to use WS-Security. It assumes you've already got a basic client and server running. If you don't, please refer to the previous sections for how to set this up.
The first thing you need to do is configure the Mule CXF connector to load up an external CXF configuration file. This configuration file is where we'll configure WS-Security.
<mule-configuration id="myConfiguration" version="1.0">
<connector name="cxf"
className="org.mule.providers.soap.cxf.CxfConnector">
<properties>
<property name="configurationLocation" value="my-cxf-config.xml" />
</properties>
</connector>
The UsernameToken feature in WS-Security is an interoperable way to exchange security tokens inside a SOAP message. In the following section we'll take a look at how to configure the client and server to exchange a username/password security token.
On the server side we need to:
In the Mule configuration we need to configure our server. You should already be familiar with how to do this, but for completeness sake, here is the configuration for this example:
<mule-descriptor name="greeterService"
implementation="org.apache.hello_world_soap_http.GreeterImpl"
singleton="true">
<inbound-router>
<endpoint address="cxf:http://localhost:63081/greeter">
<properties>
<property name="wsdlLocation" value="hello_world.wsdl" />
</properties>
</endpoint>
</inbound-router>
</mule-descriptor>
We need to write a CXF configuration file which contains our server configuration.
The <jaxws:server> element is what we use to configure the incoming interceptors on our service. The name attribute is very important. It is the QName of the WSDL port you wish to apply this configuration to. It is in the form your {service-namespace}local-port.
To determine your service namespace and port name go to your WSDL url (just append ?wsdl to your service address in the browser). The "targetNamespace" attribute on the <definitions> element is your service namespace. The <port> element near the bottom contains a "name" attribute which is your port name.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://cxf.apache.org/core"
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/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<jaxws:server name="{http://apache.org/hello_world_soap_http}SoapPort" createdFromAPI="true">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordCallbackRef" value-ref="serverCallback"/>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:server>
<bean id="serverCallback" class="org.mule.providers.soap.cxf.wssec.ServerPasswordCallback"/>
...
</beans>
Key things to note here:
Our server callback simplify verifies the password. It does this by supplying the password which will be compared to the incoming password.
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 {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if (pc.getIdentifer().equals("joe")) {
// set the password on the callback. This will be compared to the
// password which was sent from the client.
pc.setPassword("password");
}
}
}
On the client side, we need to:
Here's a simple example which configures a CXF outbound endpoint:
<mule-descriptor
name="cxfClient"
implementation="org.mule.providers.soap.cxf.jaxws.ClientMessageGenerator"
inboundEndpoint="quartz.in"
singleton="true">
<!-- An outbound endpoint which submits messages via a CXF client -->
<outbound-router>
<router className="org.mule.routing.outbound.OutboundPassThroughRouter">
<endpoint address="cxf:http://localhost:63081/greeter">
<properties>
<property name="clientClass" value="org.apache.hello_world_soap_http.SOAPService" />
<property name="port" value="SoapPort" />
<property name="wsdlLocation" value="/org/mule/providers/soap/cxf/wsa/hello_world.wsdl" />
<property name="operation" value="greetMe" />
</properties>
</endpoint>
</router>
</outbound-router>
</mule-descriptor>
We also need to add a configuration section to your "my-cxf-config.xml" file.
NOTE: if your client and your server are on separate machines, you will have two separate files, and then a CXF connector configuration on each one.
<jaxws:client name="{http://apache.org/hello_world_soap_http}SoapPort" createdFromAPI="true">
<jaxws:outInterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="user" value="joe" />
<entry key="passwordType" value="PasswordDigest" />
<!-- The callback supplies the password so its not stored in our config file -->
<entry key="passwordCallbackRef" value-ref="clientCallback" />
</map>
</constructor-arg>
</bean>
</jaxws:outInterceptors>
</jaxws:client>
<bean id="clientCallback" class="org.mule.providers.soap.cxf.wssec.ClientPasswordCallback"/>
In this configuration snippet we're:
Here is an example client password callback. As you can see, it is fairly simple. It just sets the password you want to use for the outgoing invocation:
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 {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
// set the password for our message.
pc.setPassword("yourpassword");
}
}