Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

CXF relies on WSS4J in large part to implement WS-Security. Within your own services, WS-Security can be activated by using WS-SecurityPolicy, which provides a comprehensive and sophisticated validation of the security properties of a received message. A non-WS-SecurityPolicy approach is usually also possible by way of CXF interceptors added to your service and/or client as detailed in this article.

Please note that there are some incompatibilities between WSS4J 1.6.x (used by Apache CXF 2.6.x and 2.7.x) and 2.0.x (used by Apache CXF 3.0.x and 3.1.x). The examples and links on this page mainly pertain to WSS4J 2.0.x and hence CXF 3.0.x. For more information on the changes in WSS4J 2.0.x please see the following migration page.

Overview of encryption and signing

...

On the Server side, you'll want to add the interceptors to your CXF Endpoint. If you're publishing your service using the JAX-WS APIs, you can get your CXF endpoint like this:

Code Block
java
java

import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.EndpointImpl;

EndpointImpl jaxWsEndpoint = (EndpointImpl) javax.xml.ws.Endpoint.publish("http://host/service", 
    myServiceImpl);
Endpoint cxfEndpoint = jaxWsEndpoint.getServer().getEndpoint();

If you've used the (JaxWs)ServerFactoryBean, you can simply access it via the Server object:

Code Block
java
java

import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.frontend.ServerFactoryBean;

ServerFactoryBean factory = ...;
...
Server server = factory.create();
Endpoint cxfEndpoint = server.getEndpoint();

On the client side, you can obtain a reference to the CXF endpoint using the ClientProxy helper:

Code Block
java
java

import org.apache.cxf.frontend.ClientProxy;
...

GreeterService gs = new GreeterService();
Greeter greeter = gs.getGreeterPort();
...
org.apache.cxf.endpoint.Client client = ClientProxy.getClient(greeter);
org.apache.cxf.endpoint.Endpoint cxfEndpoint = client.getEndpoint();

Anchor
addinterceptors
addinterceptors
Now you're ready to add the interceptors:

Code Block
java
java

import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
...

Map<String,Object> inProps = new HashMap<String,Object>();
... // how to configure the properties is outlined below;

WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps);
cxfEndpoint.getInInterceptors().add(wssIn);

Map<String,Object> outProps = new HashMap<String,Object>();
... // how to configure the properties is outlined below;

WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
cxfEndpoint.getOutInterceptors().add(wssOut);

...

If you're using Spring to build endpoints (e.g., web services running on a servlet container such as Tomcat), you can easily accomplish the above using your bean definitions instead.

Code Block
xml
xml

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

<jaxws:endpoint id="myService"
   implementor="com.acme.MyServiceImpl"
   address="http://localhost:9001/MyService">

   <bean id="myPasswordCallback"
      class="com.mycompany.webservice.ServerPasswordCallback"/>

   <jaxws:inInterceptors>
      <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
         <constructor-arg>
            <map>
               <entry key="action" value="UsernameToken"/>
               <entry key="passwordType" value="PasswordDigest"/>
               <entry key="signaturePropFile" value="..."/>
               <entry key="passwordCallbackRef">
                  <ref bean="myPasswordCallback"/>
               </entry>
               ...
            </map>
         </constructor-arg>
      </bean>
   </jaxws:inInterceptors>
</jaxws:endpoint>

The entry keys and values given in the constructor-arg element above (action, signaturePropFile, etc.) map to the text strings in WSS4J's WSHandlerConstants and WSConstants classes for the corresponding WSHandlerConstants.XXXXX and WSConstants.XXXX constants you see in the section below (also see the WSS4J configuration page). So by viewing WSHandlerConstants, for example, you can see that the WSHandlerConstants.USERNAME_TOKEN value given below would need to be "UsernameToken" instead when doing Spring configuration.

If you want to avoid looking up the text keys for the WSHandlerConstants.XXXXX and WSConstants.XXXX constants, you can also use the Spring util namespace to reference static constants in your Spring context as shown below.

Code Block
xml
xml

<beans
  ...
  xmlns:util="http://www.springframework.org/schema/util"
  ...
  xsi:schemaLocation="
        ...
        http://www.springframework.org/schema/util 
        http://www.springframework.org/schema/util/spring-util.xsd">

  ...

  <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
    <constructor-arg>
      <map>
        <entry value="UsernameToken">
          <key>
            <util:constant 
                static-field="org.apache.wswss4j.securitydom.handler.WSHandlerConstants.ACTION"/>
          </key>
        </entry>
        ...
      </map>
    </constructor-arg>
  </bean>

  ...  

...

As of CXF 2.2.8, the CryptoCoverageChecker interceptor allows one to validate signature and encryption coverage of message contents without migrating to a WS-SecurityPolicy based configuration. The interceptor can support enforcement of signature and encryption coverage at both the element and content level (be aware that the combination of signature and content do not represent a valid combination of coverage type and coverage scope). To configure this interceptor using the API, follow the example below.

Code Block
java
java

import org.apache.cxf.ws.security.wss4j.CryptoCoverageChecker;
import org.apache.cxf.ws.security.wss4j.CryptoCoverageChecker.XPathExpression;
import org.apache.cxf.ws.security.wss4j.CryptoCoverageUtil.CoverageScope;
import org.apache.cxf.ws.security.wss4j.CryptoCoverageUtil.CoverageType;

Map<String, String> prefixes = new HashMap<String, String>();
        prefixes.put("ser", "http://www.sdj.pl");
        prefixes.put("soap", "http://schemas.xmlsoap.org/soap/envelope/");

List<XPathExpression> xpaths = Arrays.asList(
    new XPathExpression("//ser:Header", CoverageType.SIGNED, 
        CoverageScope.ELEMENT),
    new XPathExpression("//soap:Body", CoverageType.ENCRYPTED, 
        CoverageScope.CONTENT));

CryptoCoverageChecker checker = new CryptoCoverageChecker(prefixes, xpaths);

...

The default configuation is that the SOAP Body, (WSU) Timestamp and WS-Addressing ReplyTo and FaultTo headers must be signed (if they exist in the message payload). This provides an out-of-the-box way of preventing XML Signature wrapping attacks. All that is required is that the DefaultCryptoCoverageChecker be added to the in-interceptor chain. For example:

Code Block
xml
xml

<jaxws:inInterceptors>
    <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Signature Timestamp"/>
                <entry key="signaturePropFile" value="..."/>
                <entry key="passwordCallbackClass"value="..."/>
           </map>
        </constructor-arg>
    </bean>
    <bean class="org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker"/>
</jaxws:inInterceptors>

...

As of CXF 2.0.10 and 2.1.4, you can specify custom WSS4J Processor configurations on the WSS4JInInterceptor. To activate this configuration option, one provides a non-WSS4J defined property, wss4j.processor.map, to the WSS4JInInterceptor as shown in the following Spring example. The same configuration can be achieved through the API as well. The key value is an XML qualified name of the WS-Security header element to process with the given processor implementation. The entry values can be a String representing a class name of the processor to instantiate, an Object implementing Processor, or null to disable processing of the given WS-Security header element.

Code Block
xml
xml

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
  <constructor-arg>
    <map>
      ...
      <!-- This reconfigures the processor implementation that WSS4j uses to 
               process a WS-Security Signature element. -->
      <entry key="wss4j.processor.map">
        <map key-type="javax.xml.namespace.QName">
          <entry value="my.class">
            <key>
              <bean class="javax.xml.namespace.QName">
                <constructor-arg value="http://www.w3.org/2000/09/xmldsig#"/>
                <constructor-arg value="Signature"/>
              </bean>
            </key>
          </entry>
        </map>
      </entry>
      ...
    </map>
  </constructor-arg>
</bean>

...

As of CXF 2.2.6, you can specify custom WSS4J Action configurations on the WSS4JOutInterceptor. To activate this configuration option, one provides a non-WSS4J defined property, wss4j.action.map, to the WSS4JOutInterceptor as shown in the following Spring example. The same configuration can be achieved through the API as well. The key value is an integer representing the WSS4J action identifier. The entry values can be a String representing a class name of the action to instantiate or an Object implementing Action. This configuration option allows you to override built-in action implementations or add your own.

Code Block
xml
xml

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
  <constructor-arg>
    <map>
      ...
      <!-- Redefines the action for SAMLTokenSigned 
               to use a custom implementation.  -->
      <entry key="wss4j.action.map">
        <map key-type="java.lang.Integer" value-type="java.lang.Object">
          <entry key="0x10" value-ref="mySamlTokenSignedAction"/>
        </map>
      </entry>      ...
    </map>
  </constructor-arg>
</bean>

...

  • "ws-security.enable.nonce.cache" - Whether to cache UsernameToken nonces. The default value (for CXF 2.6.0) is "true" for message recipients, and "false" for message initiators. Set it to true to cache for both cases. The default value for CXF 2.4.x and 2.5.x is false. See here for more information.
  • "ws-security.nonce.cache.instance" - This holds a reference to a ReplayCache instance used to cache UsernameToken nonces. The default instance that is used is the EHCacheReplayCache.
  • "ws-security.cache.config.file" - Set this property to point to a configuration file for the underlying caching implementation. The default configuration file that is used is cxf-ehcache.xml in the cxf-rt-ws-security module.

For the server side, you'll want to set up the following properties on your WSS4JInInterceptor (see above for code sample):

Code Block

inProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// Password type : plain text
inProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// for hashed password use:
//properties.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
// Callback used to retrieve password for given user.
inProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, 
    ServerPasswordHandler.class.getName());

The password callback class allows you to retrieve the password for a given user so that WS-Security can determine if they're authorized. Here is a small example:

Code Block
java
java

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.wswss4j.common.securityext.WSPasswordCallback;

public class ServerPasswordCallback implements CallbackHandler {

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

        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        if (pc.getIdentifier().equals("joe")) {
            // set the password on the callback. This will be compared to the
            // password which was sent from the client.
            pc.setPassword("password");
        }
    }

}

Note that for up to and including CXF 2.3.x, the password validation of the special case of a plain-text password (or any other yet unknown password type) is delegated to the callback class, see org.apache.ws.security.processor.UsernameTokenProcessor#handleUsernameToken() method javadoc of the WSS4J project. In that case, the ServerPasswordCallback should be something like the following one:

Code Block
java
java

public class ServerPasswordCallback implements CallbackHandler {

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

        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        if (pc.getIdentifer().equals("joe") {
           if (!pc.getPassword().equals("password")) {
                throw new IOException("wrong password");
           }
        }
    }

}

For CXF 2.4 onwards, the callback handler supplies the password for all cases, and the validation is done internally (but can be configured). See here for more information.
On the Client side you'll want to configure the WSS4J outgoing properties:

Code Block

outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// Specify our username
outProps.put(WSHandlerConstants.USER, "joe");
// Password type : plain text
outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// for hashed password use:
//properties.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
// Callback used to retrieve password for given user.
outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, 
    ClientPasswordHandler.class.getName());

Once again we're using a password callback, except this time instead of specifying our password on the server side, we're specifying the password we want sent with the message. This is so we don't have to store our password in our configuration file.

Code Block
java
java

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.wswss4j.common.securityext.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("password");
    }

}

In the case of multiple users with different passwords, use the WSPasswordCallback's getIdentifier() method to obtain the username of the current SOAP request.

...

1. Creating private key with given alias and password like "myAlias"/"myAliasPassword" in keystore (protected by password for
security reasons)

Code Block

keytool -genkey -alias myAlias -keypass myAliasPassword -keystore \ 
  privatestore.jks -storepass keyStorePassword -dname "cn=myAlias" -keyalg RSA

...

2. Self-sign our certificate (in production environment this will be done by a company like Verisign).

Code Block

keytool -selfcert -alias myAlias -keystore privatestore.jks \ 
    -storepass keyStorePassword -keypass myAliasPassword

3. Export the public key from our private keystore to file named key.rsa

Code Block

keytool -export -alias myAlias -file key.rsa -keystore privatestore.jks \ 
    -storepass keyStorePassword

4. Import the public key to new keystore:

Code Block

keytool -import -alias myAlias  -file key.rsa -keystore publicstore.jks \ 
    -storepass keyStorePassword

...

On the client side, our outgoing WS-Security properties will look like so (see above for code sample):

Code Block
java
java

outProps.put(WSHandlerConstants.ACTION, "Signature");
outProps.put(WSHandlerConstants.USER, "myAlias");
outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, 
    ClientCallbackHandler.class.getName());
outProps.put(WSHandlerConstants.SIG_PROP_FILE, "client_sign.properties");

...

Tip
titleTip

For X.509 support you will normally have multiple actions, e.g. Encryption with Signature. For these cases, just space-separate the actions in the ACTION property as follows:

Code Block
java
java

outProps.put(WSHandlerConstants.ACTION, 
    WSHandlerConstants.TIMESTAMP + " " + 
    WSHandlerConstants.SIGNATURE + " " + 
    WSHandlerConstants.ENCRYPT);

Alternatively, you may space-separate the string literals you see above in the Spring configuration (e.g., "Signature Encrypt")

Our client_sign.properties file contains several settings to configure WSS4J:

Code Block

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=keyStorePassword
org.apache.ws.security.crypto.merlin.keystore.alias=myAlias
org.apache.ws.security.crypto.merlin.keystore.file=client_keystore.jks

On the server side, we need to configure our incoming WSS4J interceptor to verify the signature using the Client's public key.

Code Block
java
java

inProps.put(WSHandlerConstants.ACTION, "Signature");
inProps.put(WSHandlerConstants.SIG_PROP_FILE, "server.properties");

Our server_sign.properties file contains several settings to configure WSS4J:

Code Block

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=amex123
org.apache.ws.security.crypto.merlin.keystore.file=server_keystore.jks

...