Java Web services: Granular use of WS-Security

Applying WS-Security at the operation or message level

WS-Security for SOAP Web services doesn't have to be an all-or-nothing proposition. By configuring WS-Security at the operation or message level, you can apply an appropriate degree of protection to every exchange, reducing or eliminating the WS-Security overhead for operations that don't need full protection. Dennis Sosnoski continues his Java Web services series with a look at granular WS-Security in Web Services Description Language (WSDL) using Apache Axis2 and Rampart.

Dennis Sosnoski, Consultant, Sosnoski Software Solutions, Inc.

Author photoDennis Sosnoski is a consultant and trainer specializing in Java-based XML and Web services. His professional software development experience spans more than 30 years, with the last 10 focused on server-side XML and Java technologies. Dennis is the lead developer of the open source JiBX XML Data Binding framework and the associated JiBX/WS Web services framework, as well as a committer on the Apache Axis2 Web services framework. He was also one of the Expert Group members for the JAX-WS 2.0 and JAXB 2.0 specifications. The material for the Java Web Services series is based on Dennis' training classes.



04 August 2009

Also available in Chinese Russian Vietnamese Spanish

In simple Web service environments, clients connect directly to servers, and the servers directly perform all the necessary processing for the request. As you learned in the last article of this series, SSL-secured connections can provide excellent security for most purposes in this type of environment. But more complex environments are increasingly common, where multiple layers of servers are involved in processing a request. The whole idea of service orchestration, increasingly popular in many enterprise environments, is based on this approach, as is the concept of service-oriented architecture (SOA). In these types of environments, the more powerful alternative of WS-Security is necessary.

About this series

Web services are a crucial part of Java™ technology's role in enterprise computing. In this series of articles, XML and Web services consultant Dennis Sosnoski covers the major frameworks and technologies that are important to Java developers using Web services. Follow the series to stay informed of the latest developments in the field and aware of how you can use them to aid your programming projects.

WS-Security comes with a heavy performance cost, as discussed in the last article. One way of reducing that cost is to set the WS-SecurityPolicy appropriate for each individual operation or even message defined by a service, rather than applying a single WS-SecurityPolicy to the entire service. Granular use of WS-Security requires more consideration than the one-size-fits-all approach, but when properly applied it can reduce the performance overhead for commonly used operations without weakening the security of operations that need it.

Defining a policy

The sample application used for this article is the same one used in "Axis2 WS-Security basics" and "Axis2 WS-Security signing and encryption" — a simple library-management service. (See Download to get the full source code for this article.) This service defines three operations:

  • getBook to retrieve the details for a particular book identified by International Standard Book Number (ISBN).
  • getBooksByType to retrieve the details for all books of a particular type.
  • addBook to add a new book to the library.

To give some interesting variety in the security usage, this article assumes that:

  • The getBook operation can safely be exposed to anyone (no security).
  • The getBooksByType needs authorization (so requires a UsernameToken).
  • The addBook operation needs an audit trail to track who added each book (implemented by signing of the request messages).

In the earlier articles, you saw how to configure Axis2/Rampart by attaching a WS-SecurityPolicy document to the org.apache.axis2.client.ServiceClient instance (on the client side) or by embedding the policy document in the services.xml service configuration (on the server side). This approach works and can be useful in testing, but for production use WS-SecurityPolicy is best associated directly with a service definition by being embedded within a WSDL document. WS-Policy and WS-SecurityPolicy are designed to support this type of embedding, with references from <wsdl:binding>, <wsdl:binding>/<wsdl:operation>, or <wsdl:binding>/<wsdl:operation>/<wsdl:message> definitions used to identify the appropriate policy to be applied to that binding, operation, or message. Axis2 1.4.1 implemented preliminary handling for policies embedded in WSDL, and the implementation has improved in the current Axis2 1.5 release code. To demonstrate the use of policy in WSDL, this article uses the Axis2 1.5 release code in combination with the as-yet unreleased current Rampart code (which should eventually be released as Rampart 1.5).

Listing 1 shows the WSDL for the example application with policy added and referenced from the appropriate locations. (Listing 1 is edited for length and width; the full WSDL is available as library.wsdl in the code download.) Each policy defines an Id value that is then referenced from the appropriate operation (in the case of the UsernameToken policy) or message (in the case of the signing policy), all as shown in bold.

Listing 1. WSDL with granular security policies
<wsdl:definitions targetNamespace="http://ws.sosnoski.com/library/wsdl"
    xmlns:wns="http://ws.sosnoski.com/library/wsdl"
    xmlns:tns="http://ws.sosnoski.com/library/types"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">

  <!-- Policy for signing message, with certificate from client included in each
    message to server -->
  <wsp:Policy wsu:Id="SignOnly" xmlns:wsu=
      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <sp:AsymmetricBinding
            xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
          <wsp:Policy>
            <sp:InitiatorToken>
              <wsp:Policy>
                <sp:X509Token sp:IncludeToken=".../IncludeToken/AlwaysToRecipient"/>
              </wsp:Policy>
            </sp:InitiatorToken>
            ...
          </wsp:Policy>
        </sp:AsymmetricBinding>
        <sp:SignedParts
            xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
          <sp:Body/>
        </sp:SignedParts>

      </wsp:All>
    </wsp:ExactlyOne>
  </wsp:Policy>

  <!-- Policy for UsernameToken with plaintext password, sent from client to
    server only -->
  <wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <sp:SupportingTokens
            xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
          <wsp:Policy>
            <sp:UsernameToken sp:IncludeToken=".../IncludeToken/AlwaysToRecipient"/>
          </wsp:Policy>
        </sp:SupportingTokens>
      </wsp:All>
    </wsp:ExactlyOne>
  </wsp:Policy>
  ...
  <wsdl:binding name="LibrarySoapBinding" type="wns:Library">

    <wsdlsoap:binding style="document"
      transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getBook">
      <wsdlsoap:operation soapAction="urn:getBook"/>
      <wsdl:input name="getBookRequest">
        <wsdlsoap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getBookResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>

    <wsdl:operation name="getBooksByType">
      <wsp:PolicyReference
          xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
          URI="#UsernameToken"/>
      <wsdlsoap:operation soapAction="urn:getBooksByType"/>
      <wsdl:input name="getBooksByTypeRequest">
        <wsdlsoap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getBooksByTypeResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>

    <wsdl:operation name="addBook">
      <wsdlsoap:operation soapAction="urn:addBook"/>
      <wsdl:input name="addBookRequest">
        <wsp:PolicyReference
            xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
            URI="#SignOnly"/>
        <wsdlsoap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="addBookResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>

  </wsdl:binding>
  <wsdl:service name="library-granular">
    ...
  </wsdl:service>
</wsdl:definitions>

The Listing 1 policies and WSDL are all taken from the earlier articles, though not previously merged in this way. You might notice one significant difference in the policies, though: the previous versions all included Rampart configuration information, which was specific to either the client or the server. Now that the policy is embedded into the WSDL, it's not practical to include the Rampart configuration directly. (You'd need to edit the WSDL to include your client Rampart configuration information and regenerate code every time this changed, and on the server your Rampart configuration would be exposed to anyone accessing the WSDL.) So the example code instead sets this configuration information separately. To do this, it uses variations of the same techniques previously used for the policies with Rampart configuration included.


Client-side usage

Generating code from WSDL works the same from the user perspective whether WS-Policy is included or not. If you look inside the client stub generated from a WSDL containing WS-Policy, you'll see that the policy is attached directly to the components of the service description as it is being constructed, but this is hidden inside the implementation and does not affect the interface methods that your client code uses.

To make use of the WS-SecurityPolicy configuration on the client, you do need to take some action in your client code. At a minimum, you must engage the Rampart module on the org.apache.axis2.client.ServiceClient associated with your stub instance. This step is necessary even if you include your Rampart configuration information in the WSDL. Unfortunately, there does not appear to be any working way to engage Rampart at the operation or message level in the current code, so part of the benefit of granular WS-Security is lost at present when it's used with an Axis2 client.

If you keep your Rampart configuration separate from the WSDL, as recommended, you also need to apply that configuration to the service description. Listing 2 shows the client code used for this purpose in the sample application. It calls the applyPolicy() method to add the policy containing the Rampart configuration to the service definition.

Listing 2. Configuring client-side operations
// create the client stub
String target = args[0] + "://" + args[1] + ":" + args[2] + args[3];
System.out.println("Connecting to " + target);
LibraryGranularStub stub = new LibraryGranularStub(target);

// configure and engage rampart module
ServiceClient client = stub._getServiceClient();
client.getAxisService().applyPolicy(loadPolicy("rampart-client-policy.xml"));
client.engageModule("rampart");

// set the username and password for requests which use them
Options options = client.getOptions();
options.setUserName("libuser");
options.setPassword("books");

The Listing 2 code sets the username and password on the ServiceClient options, meaning these are defined for all operations using that service even though they're only used by a single operation. Unlike engaging the Rampart module for all operations, setting the username and password in this way does no harm — the values are used only when needed by Rampart to construct a UsernameToken, and are otherwise ignored.

The policy document containing the Rampart configuration is shown in Listing 3. This is the same policy used in "Axis2 WS-Security signing and encryption," now extracted into a separate policy document.

Listing 3. Rampart client configuration policy
<wsp:Policy xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
  <wsp:ExactlyOne>
    <wsp:All>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
        <ramp:user>clientkey</ramp:user>
        <ramp:passwordCallbackClass
          >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
        
        <ramp:signatureCrypto>
          <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
            <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
              >JKS</ramp:property>
            <ramp:property name="org.apache.ws.security.crypto.merlin.file"
              >client.keystore</ramp:property>
            <ramp:property
              name="org.apache.ws.security.crypto.merlin.keystore.password"
              >nosecret</ramp:property>
          </ramp:crypto>
        </ramp:signatureCrypto>
        
      </ramp:RampartConfig>
    
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

The Ant build.xml included in the code download handles generating both client and server code from the WSDL, compiling the code, and generating the service AAR file. It also copies the Rampart configuration policy (supplied in the download as rampart-client-policy.xml, in the root directory) into the client classpath, along with performing some server-side policy handling discussed in the next section.


Server-side usage

If you want to keep your server-side Rampart configuration out of the WSDL (generally a very good idea!) you need to include it in the services.xml file instead. In earlier articles, you saw this done for a complete WS-SecurityPolicy configuration, including the Rampart configuration, and applied to the service as a whole. This time around, the Rampart configuration policy is the only part added to the services.xml, and it's done at the operation level.

The policy document containing the Rampart configuration is shown in Listing 4. Like the client-side equivalent, this is the same policy used in "Axis2 WS-Security signing and encryption," now extracted into a separate policy document.

Listing 4. Rampart server configuration policy
<wsp:Policy xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
  <wsp:ExactlyOne>
    <wsp:All>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
        <ramp:user>serverkey</ramp:user>
        <ramp:passwordCallbackClass
          >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
        
        <ramp:signatureCrypto>
          <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
            <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
              >JKS</ramp:property>
            <ramp:property name="org.apache.ws.security.crypto.merlin.file"
              >server.keystore</ramp:property>
            <ramp:property
              name="org.apache.ws.security.crypto.merlin.keystore.password"
              >nosecret</ramp:property>
          </ramp:crypto>
        </ramp:signatureCrypto>
        
      </ramp:RampartConfig>
    
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

The Ant build.xml uses a PolicyTool program included in the code download to merge the Listing 4 policy (supplied in the download as rampart-server-policy.xml, in the root directory) into the services.xml. The modified services.xml is shown in Listing 5 (edited for width and length):

Listing 5. services.xml after additions
<serviceGroup>
  <service name="library-granular">
    <messageReceivers>
      <messageReceiver class=
        "com.sosnoski.ws.library.adb.LibraryGranularMessageReceiverInOut"
        mep="http://www.w3.org/ns/wsdl/in-out"/>
    </messageReceivers>
    <parameter name="ServiceClass"
      >com.sosnoski.ws.library.adb.LibraryGranularImpl</parameter>
    <parameter name="useOriginalwsdl">true</parameter>
    <parameter name="modifyUserWSDLPortAddress">true</parameter>
    <operation mep="http://www.w3.org/ns/wsdl/in-out" name="getBook"
      namespace="http://ws.sosnoski.com/library/wsdl">
      <actionMapping>urn:getBook</actionMapping>
      <outputActionMapping>.../getBookResponse</outputActionMapping>
    </operation>
    <operation mep="http://www.w3.org/ns/wsdl/in-out" name="getBooksByType"
      namespace="http://ws.sosnoski.com/library/wsdl">
      <actionMapping>urn:getBooksByType</actionMapping>
      <outputActionMapping>.../getBooksByTypeResponse</outputActionMapping>

<module ref="rampart"/>
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <wsp:ExactlyOne>
  <wsp:All>

    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
    <ramp:user>serverkey</ramp:user>
    ...

    </ramp:RampartConfig>

  </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>
  </operation>
    <operation mep="http://www.w3.org/ns/wsdl/in-out" name="addBook" 
    namespace="http://ws.sosnoski.com/library/wsdl">
      <actionMapping>urn:addBook</actionMapping>
      <outputActionMapping>
        http://ws.sosnoski.com/library/wsdl/Library/addBookResponse
      </outputActionMapping>

<module ref="rampart"/>
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <wsp:ExactlyOne>
  <wsp:All>

    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
    <ramp:user>serverkey</ramp:user>
    ...

    </ramp:RampartConfig>

  </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>
  </operation>
  </service>
</serviceGroup>

Adding policy to services.xml the easy way

"Axis2 WS-Security basics" introduced a tool for adding module references and policies to a generated services.xml, obviating the need to edit this file by hand each time you regenerate your server-side code. This article uses an improved version of the tool that allows you to perform additions at the operation level of the service configuration, and to perform multiple additions with a single execution of the tool. The improved version is com.sosnoski.ws.PolicyTool; source and binary versions are in the policytool directory of the code download. See the readme.txt file in that directory for instructions.

In Listing 5, you can see that a Rampart module reference and a copy of the server configuration policy have been added to the definitions of the two operations using WS-Security, getBooksByType, and addBook. These changes configure the service to engage Rampart only for those two operations, and to use the supplied settings for Rampart's key and certificate access.

If you build and deploy the generated AAR file to an Axis2 server installation, you can see the effect of this in the Administration/Available Services page, where the modules engaged for each operation defined on the service are listed. If you run the client code and monitor the message exchange (using a tool such as TCPMon, described in "Axis2 WS-Security basics"), you'll see that the security handling works as desired, with:

  • No WS-Security headers on the getBook messages
  • A WS-Security header with a UsernameToken on the getBooksByType request message
  • A WS-Security header with signatures on the addBook request (but not the response) message

Axis2/Rampart limitations

Axis2 and Rampart are currently (that is, with the current Axis2 1.5 release and a nightly build of Rampart) able to handle basic WS-SecurityPolicy configurations embedded in WSDL, including granular policies defined at the operation or message level. However, some of the configurations tested for this article would not work with Axis2/Rampart. For instance, trying to set the UsernameToken policy for the addBook operation (in addition to the signing policy on the input message) resulted in an error apparently related to encryption in the Rampart code (even though no encryption was being used). Judging from this experience, and from some of the problem reports, it looks like the Axis2/Rampart WS-SecurityPolicy handling is good for simple configurations reflecting common use cases but prone to failure when applied to unusual combinations.

Another issue is that the overhead of engaging Rampart on the server will always occur on both request and response handling even if you're using WS-Security for only one of the messages (as discussed in "The high cost of (WS-)Security"). On the client side, the situation is even worse — because engaging Rampart on the client is currently an all-or-nothing proposition, if you use WS-Security on any operation you'll have to live with the Rampart overhead on every operation.

Finally, the current Axis2/Rampart WS-Policy handling does not support policy alternatives. Alternatives are a major feature of the WS-Policy design, allowing services to let clients choose which of two or more acceptable configurations are to be used when they access the service. The Axis2/Rampart policy implementation does not support this feature, instead ignoring everything except the first alternative.


Moving on

With this article and the previous three, the Java Web services series has covered all the major aspects of WS-Security and WS-SecurityPolicy handling in Axis2/Rampart, including performance issues. In future installments, you'll see how other Java Web services frameworks work with WS-Security and WS-SecurityPolicy, but first there are a couple other aspects of Axis2 to look into.

One issue that is important to many organizations is the support for Java Architecture for XML Binding (JAXB) 2.X. JAXB 2.X is the official Java standard for XML data binding, and although open source alternatives offer their own benefits, some organizations want to stick to the standard. In the next article, you'll see how you can use JAXB 2.X data binding with Axis2, and learn how it compares to the other data-binding choices supported by Axis2.


Download

DescriptionNameSize
Source code for this articlej-jws7.zip29KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, SOA and web services, Open source
ArticleID=418048
ArticleTitle=Java Web services: Granular use of WS-Security
publish-date=08042009