Java web services: Modeling and verifying WS-SecurityPolicy

Common errors in working with WS-SecurityPolicy, and how WS-SecurityPolicy can be modeled in Java for verification and transformation

WS-SecurityPolicy lets you define security configurations as part of your Web Service Description Language (WSDL) service description. It's a powerful tool, but working with WS-SecurityPolicy documents can be painful. Assertions must be correctly structured to be effective, and version namespaces need to be consistent. In this article, you'll learn about common errors made in creating WS-SecurityPolicy documents, and you'll see how WS-Policy and WS-SecurityPolicy can be modeled in Java™ for verification and transformation.

Dennis Sosnoski, Architecture Consultant and Trainer, Sosnoski Software Solutions, Inc.

Author photoDennis Sosnoski is a consultant and trainer specializing in Java-based SOA 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' SOA and web services training classes.



19 April 2011

Also available in Chinese Russian Japanese

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.

You've seen in earlier articles in this series how to use WS-SecurityPolicy in WSDL service descriptions to configure WS-Security handling. One downside of WS-SecurityPolicy is that it's easy to make a mistake in the construction of a policy, such as by using an assertion in the wrong place or by mixing assertion versions within a document. Many web services stacks silently ignore these types of mistakes, meaning your policy doesn't function as intended, and you're left to puzzle through the policy to try to find the cause.

In the first part of this article, you'll learn why WS-SecurityPolicy can be difficult to work with and see the causes of common WS-SecurityPolicy errors. The second part introduces a data model for WS-Policy and WS-SecurityPolicy that supports verifying the structure of documents, then shows how JiBX data binding can be used to unmarshal documents to the model.

WS-Policy in WSDL

Namespace usage

This article uses:

  • The wsdl prefix to represent the WSDL 1.1 http://schemas.xmlsoap.org/wsdl/ namespace
  • The wsp prefix for the http://schemas.xmlsoap.org/ws/2004/09/policy namespace used by the submission version of WS-Policy
  • The sp prefix for the http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702 namespace used by WS-SecurityPolicy 1.2

"Understanding WS-Policy" covered most aspects of WS-Policy in detail, including how policy assertions are attached to WSDL 1.1 components. In brief, WS-Policy allows you to attach polices to WSDL service definitions at several different points corresponding to messages and groupings of messages. The four levels of message groupings WS-Policy uses are:

  • Message - Policy applies to a particular message (anywhere that message is used if the policy is attached through the <wsdl:message> element, or when used by a particular operation if attached through the input/output/fault definitions of the operation in either the <wsdl:portType> or the <wsdl:binding> element).
  • Operation - Policy applies to all message exchanges for a particular operation (policy attached through the <wsdl:operation> element, within either the <wsdl:binding> or <wsdl:portType>).
  • Endpoint - Policy applies to all message exchanges for a particular service binding (policy attached through <wsdl:port> or <wsdl:binding>) or to all service bindings based on a particular port type (policy attached to that <wsdl:portType>).
  • Service - Policy applies to all endpoints and all operations associated with a service (policy attached at the <wsdl:service> element).

Policies applied at one level of the message groupings are inherited by lower layers, so that the actual (or effective, in WS-Policy terms) policy applied to each message is the conjunction of all policies applied at the message, operation, endpoint, and service layers. So policy is determined not just by the message itself, but also by the context in which the message is used.

WS-Policy attachment techniques

In "Understanding WS-Policy," I covered the most common way of attaching WS-Policy assertions to WSDL components: using the <wsp:PolicyReference> element. This type of attachment by reference is convenient for most purposes, but it's not the only technique. You can instead use a special attribute — wsp:PolicyURIs — on any WSDL element corresponding to one of the message-grouping levels. The attribute's value is a list of policy-assertion URIs to be applied to the element. You can also attach policy assertions to a WSDL element by just embedding the policy assertion directly.

The choice among these different ways of attaching policy assertions is mostly arbitrary. The wsp:PolicyURIs attribute can be used with some WSDL elements that do not allow extension elements according to the original WSDL schema — but the more-recent WS-I WSDL schema allows extension elements on every WSDL element, so that's not an issue with most web services stacks. Using either the <wsp:PolicyReference> element or the wsp:PolicyURIs attribute allows you to reuse policy assertions, including external policy assertions (though not all web services stacks support external references).

There's also a way to attach policies externally, using a <wsp:PolicyAttachment> element. In theory, this approach allows you to associate policies with services from outside the WSDL. However, most web services stacks are not set up to process this type of attachment, instead expecting the policy information to be either referenced by or included directly in the WSDL service description.

<wsp:PolicyReference> lets you define a digest algorithm and digest value for the referenced policy, using optional attributes. The digest provides some security for external policy references, though it doesn't protect against modifications to the reference itself. <wsp:PolicyAttachment> goes further, allowing WS-Security to be used directly for securing the entire attachment.

WS-SecurityPolicy attachment restrictions

WS-SecurityPolicy specifies the message-grouping levels at which certain types of policy assertions should be attached to a service description:

  • Security binding assertions: <sp:TransportBinding> only at endpoint level (because the transport is used to access the endpoint); <sp:SymmetricBinding> and <sp:AsymmetricBinding> at either endpoint or operation level
  • Supporting token assertions (all forms of supporting tokens, including <sp:SupportingTokens> and <sp:SignedSupportingTokens>, among others): endpoint, operation, or message level
  • Option assertions (<sp:Wss10>, <sp:Wss11>, <sp:Trust13>): endpoint level
  • Protection assertions (<sp:SignedParts>, <sp:SignedElements>, <sp:EncryptedParts>, <sp:EncryptedElements>, <sp:ContentEncryptedElements>, <sp:RequiredElements>, and <sp:RequiredParts>): message level

These levels are only suggestions, so web services stacks should be able to handle different attachment levels — but it's probably best to follow the suggestions whenever possible.

WS-SecurityPolicy assertions not listed above are nested within other assertions, so the placement of the containing assertion controls their level.


Working with WS-SecurityPolicy

WS-SecurityPolicy uses a complex and confusing structure that's difficult to construct or modify directly. Most commercial web services stacks provide GUI tools you can use to generate WS-SecurityPolicy documents from menus of options. (The open source Metro stack also provides this type of tool, as part of NetBeans.) The output from a tool of this type will probably work well with the web service stack supplying the tool, but it may not be in best-practices form or even completely correct. If you're not using such a tool, want to tailor a security configuration not supported by the tool, or just want to make sure you have a clean policy, you must deal head-on with the complexities of WS-SecurityPolicy.

I've never been a big fan of the GUI WS-SecurityPolicy configuration tools, so in this series I've been working with policy documents directly. In the process of testing different types of security configurations, I've made a number of errors that have given me an abiding respect for the complexity of WS-SecurityPolicy and the many ways you can mess it up. I'll start with the reasons I think WS-SecurityPolicy is complex, then cover the problems that in my experience most often interfere with developing correct and interoperable WS-SecurityPolicy configurations.

WS-SecurityPolicy structure

The WS-SecurityPolicy 1.2 schema defines about 140 elements, almost all as global definitions (meaning they can each theoretically be used stand-alone, rather than embedded inside other elements of a document). Most of these are defined as empty marker elements, with extension attributes from other namespaces allowed but no content. Almost all of the rest are defined as allowing both extension attributes and elements from other namespaces, but not from the WS-SecurityPolicy namespace. The WS-SecurityPolicy 1.3 schema just extends the 1.2 version, adding a few more elements.

Both the base 1.2 schema and the 1.3 version are useless for any practical purpose. This is not entirely the fault of the schema authors, but rather of the limitations of XML Schema itself and the structure of WS-Policy.

As discussed in "Understanding WS-Policy," WS-Policy provides a simple skeleton for defining and combining policy assertions, with the assertions themselves defined by extensions such as WS-SecurityPolicy, each using its own namespace. Listing 1 gives an example of a WS-SecurityPolicy document, with the WS-Policy elements shown in bold:

Listing 1. WS-SecurityPolicy example
<wsp:Policy wsu:Id="SecureConv"
   xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
   xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
   xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
  <wsap:UsingAddressing xmlns:wsap="http://www.w3.org/2006/05/addressing/wsdl"/>
  <sp:SymmetricBinding>
   <wsp:Policy>
    <sp:ProtectionToken>
     <wsp:Policy>
      <sp:SecureConversationToken sp:IncludeToken=
        "http://.../ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
       <wsp:Policy>
        <sp:RequireDerivedKeys/>
        <sp:BootstrapPolicy>
         <wsp:Policy>
          <sp:AsymmetricBinding>
           <wsp:Policy>
            <sp:InitiatorToken>
             <wsp:Policy>
              <sp:X509Token sp:IncludeToken=
                "http://.../ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
               <wsp:Policy>
                <sp:RequireThumbprintReference/>
               </wsp:Policy>
              </sp:X509Token>
             </wsp:Policy>
            </sp:InitiatorToken>
            <sp:RecipientToken>
             <wsp:Policy>
              <sp:X509Token sp:IncludeToken=
                "http://.../ws-securitypolicy/200702/IncludeToken/AlwaysToInitiator">
               <wsp:Policy>
                <sp:RequireThumbprintReference/>
               </wsp:Policy>
              </sp:X509Token>
             </wsp:Policy>
            </sp:RecipientToken>
            <sp:AlgorithmSuite>
             <wsp:Policy>
              <sp:TripleDesRsa15/>
             </wsp:Policy>
            </sp:AlgorithmSuite>
            <sp:Layout>
             <wsp:Policy>
              <sp:Strict/>
             </wsp:Policy>
            </sp:Layout>
            <sp:IncludeTimestamp/>
            <sp:OnlySignEntireHeadersAndBody/>
           </wsp:Policy>
          </sp:AsymmetricBinding>
          <sp:SignedParts>
           <sp:Body/>
           <sp:Header Namespace="http://www.w3.org/2005/08/addressing"/>
          </sp:SignedParts>
          <sp:EncryptedParts>
           <sp:Body/>
          </sp:EncryptedParts>
          <sp:Trust13>
           <wsp:Policy>
            <sp:MustSupportIssuedTokens/>
            <sp:RequireClientEntropy/>
            <sp:RequireServerEntropy/>
           </wsp:Policy>
          </sp:Trust13>
         </wsp:Policy>
        </sp:BootstrapPolicy>
       </wsp:Policy>
      </sp:SecureConversationToken>
     </wsp:Policy>
    </sp:ProtectionToken>
    <sp:AlgorithmSuite>
     <wsp:Policy>
      <sp:Basic128Rsa15/>
     </wsp:Policy>
    </sp:AlgorithmSuite>
    <sp:Layout>
     <wsp:Policy>
      <sp:Strict/>
     </wsp:Policy>
    </sp:Layout>
   </wsp:Policy>
  </sp:SymmetricBinding>
  <sp:EncryptedParts>
   <sp:Body/>
  </sp:EncryptedParts>
</wsp:Policy>

Sloppy sop

The WS-SecurityPolicy schema definition is basically a sop to the idea of defining an XML structure. The current published version even contains an error in one of the element names (RequireAppiesTo, which is supposed to be RequireAppliesTo), and at least one error in value types (the sp:Name attribute of the <sp:Header> element, which is supposed to be a simple name but is defined as a qualified name in the schema). These errors show that the schema has not seen much (if any) actual use.

The Listing 1 policy document consists mostly of alternating layers of WS-Policy elements (only the <wsp:Policy> element, in this case) and WS-SecurityPolicy elements (using the sp prefix). Unfortunately, XML Schema is unable to represent this type of structure. Each schema definition deals with a single namespace, and although it can reference and use elements defined in another namespace, it cannot define a substructure for those elements. So the schema definition for WS-Policy can define a general structure, and the WS-SecurityPolicy schema can define elements intended to be used in combination with that structure, but there's no way to define the interactions between these two sets of elements.

Because XML Schema is unable to define the structure of documents, the text of the WS-SecurityPolicy standard uses the same syntax representation as the WS-Policy recommendation to show the intended structure of documents. Listing 2 shows a sample taken from the standard, for the <sp:SymmetricBinding> element used in Listing 1:

Listing 2. WS-SecurityPolicy syntax sample
<sp:SymmetricBinding xmlns:sp="..." ... >
   <wsp:Policy xmlns:wsp="...">
     (
       <sp:EncryptionToken ... >
         <wsp:Policy> ... </wsp:Policy>
       </sp:EncryptionToken>
       <sp:SignatureToken ... >
         <wsp:Policy> ... </wsp:Policy>
       </sp:SignatureToken>
     ) | (
       <sp:ProtectionToken ... >
         <wsp:Policy> ... </wsp:Policy>
       </sp:ProtectionToken>
     )
     <sp:AlgorithmSuite ... > ... </sp:AlgorithmSuite>
     <sp:Layout ... > ... </sp:Layout> ?
     <sp:IncludeTimestamp ... /> ?
     <sp:EncryptBeforeSigning ... /> ?
     <sp:EncryptSignature ... /> ?
     <sp:ProtectTokens ... /> ?
     <sp:OnlySignEntireHeadersAndBody ... /> ?
     ...
   </wsp:Policy>
   ...
</sp:SymmetricBinding>

The syntax used in Listing 2 should be fairly easy for most developers to understand: using parenthesis to delimit groupings, | characters to indicate choices between alternatives, and ? to indicate optional components. (The full syntax also uses * for zero or more occurrences, and + for one or more.) It's not a standard for XML grammars, though, so there's no way to use it directly to validate instance documents.

Structure errors

Given the complexity of WS-SecurityPolicy and the difficulty of checking documents against the standard, it's not surprising that one of the most common problems in working with documents is to add assertions at the wrong point. For instance, if you were to move the <EncryptedParts> element (near the end of Listing 1) inside the preceding <SymmetricBinding> element's Policy, you'd have a structure that doesn't match the WS-SecurityPolicy standard. The interpretation of this incorrectly structured policy would be undefined and at the whim of each web services stack — it might work as hoped on one, but would likely not be interoperable with other stacks.

Web services stacks may or may not report structure errors to users. When I tried the above example of a misplaced EncryptedParts element with the latest versions of the three open source web services stacks (Apache Axis2 1.5.4, Metro 2.1, and Apache CXF 2.3.3) evaluated in earlier articles of this series, only Metro reported an error and refused to operate. Axis2 and CXF both accepted the policy without complaint but operated without encrypting the message bodies. This type of silent failure makes it hard to diagnose problems caused by policy structure errors.

Version conflicts

Namespaces are an especially messy issue with WS-Policy and WS-SecurityPolicy. The standardization of both WS-Policy and WS-SecurityPolicy took years, and earlier versions of the technologies became widely used before the standards were released. The official standards mostly follow the XML structure of the earlier versions but use different namespaces so that it's clear which set of rules apply to a particular document.

Most tools support both the official recommendations and the earlier versions of WS-Policy and WS-SecurityPolicy, allowing the versions to be used interchangeably. (Axis2 is the exception, supporting only the submission version of WS-Policy as of the current 1.5.4 release.) Despite this flexibility, using different namespaces within a single document can create problems. There's no reason to mix WS-Policy or WS-SecurityPolicy namespaces deliberately, but it can easily happen by accident when you're combining portions of other policies in order to construct a new policy. The resulting conflicts can include both the namespaces used for elements and, in the case of token-inclusion values, the actual text values. For instance, both WS-SecurityPolicy 1.2 and 1.3 use the value http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Always to say that a token should always be included in messages. The older 1.1 version instead used the value http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Always for the same purpose.

Because the meanings of XML elements are largely unchanged across versions of these standards, individual tools may choose to ignore the namespace differences when working with policy documents. Tools taking this approach will probably accept policies mixing namespaces, without any errors. In general, though, it's best to stay with the official recommendation namespace and, especially, to avoid mixing components from earlier namespaces with those from the recommendation namespace.


Modeling WS-Policy and WS-SecurityPolicy

Modeling WS-Policy and WS-SecurityPolicy in the Java language presents an entirely different set of challenges from those involved in modeling WSDL 1.1, as discussed in "Understanding and modeling WSDL 1.1." Whereas the main issue with modeling WSDL is to allow for the many element-ordering variations in common use, WS-Policy and WS-SecurityPolicy ignore element ordering completely, instead providing a loose structure with many rules imposed on top. This means that the expected content of a <wsp:Policy> element (or any of the other WS-Policy operator variations) is completely dependent on the element's context. Listing 1 demonstrates this, with different content models for almost all of the 15 <wsp:Policy> elements in the document.

Data model

WS-SecurityPolicy assertions are mostly of two types: marker assertions (empty elements), which just by their presence indicate some quality or feature; or structured assertions, with a nested policy containing other assertions. The structured assertions are where the complexity comes in, of course.

Handling for structured assertions builds on the model's NestedPolicy base class and the associated VerificationHandler interface, shown in Listing 3:

Listing 3. NestedPolicy and VerificationHandler
public abstract class NestedPolicy extends AssertionBase {
   /** Nested policy definition. */
   private Policy m_policy;
   
   /** Arbitrary extension elements. */
   private List<Element> m_extensions = new ArrayList<Element>();
   ...
   /**
    * Create a handler instance for a set of assertions.
    * 
    * @return instance
    */
   public abstract VerificationHandler createHandler();
   
   /**
    * Verify policy goodness. This uses a handler supplied by the {@link
    * #createHandler()} method to verify all the assertions within the nested policy in
    * the context of this assertion.
    * 
    * @param vctx
    */
   public void verify(ValidationContext vctx) {
       for (Set<AssertionBase> asserts : m_policy.getAlternatives()) {
           VerificationHandler handler = createHandler();
           for (AssertionBase asser : asserts) {
               if (asser instanceof ExtensibleMarker) {
                   handler.addMarker((ExtensibleMarker)asser, vctx);
               } else {
                   handler.addGeneral(asser, vctx);
               }
           }
           handler.complete(vctx);
       }
   }
}

public interface VerificationHandler
{
    /**
     * Add marker assertion.
     * 
     * @param marker
     * @param vctx
     */
    public abstract void addMarker(ExtensibleMarker marker, ValidationContext vctx);
    
    /**
     * Add general assertion (anything that's not a marker).
     * 
     * @param asser
     * @param vctx
     */
    public abstract void addGeneral(AssertionBase asser, ValidationContext vctx);
    
    /**
     * Check that the assertions included in this collection fulfill all requirements for
     * the policy. This method is only used when verifying a complete policy (one
     * particular combination of alternatives, when using alternatives).
     * 
     * @param vctx
     * @return <code>true</code> if no errors, <code>false</code>
     * if error
     */
    boolean complete(ValidationContext vctx);
}

All structured assertions extend the NestedPolicy class, implementing the createHandler() method to return an instance of the VerificationHandler interface tailored to the specific assertion structure. The VerificationHandler provides methods for accumulating and verifying the assertions contained within the nested policy, so that's where the details of the structured assertion handling are implemented. Where policy alternatives are present, the NestedPolicy.verify() method creates a separate VerificationHandler instance for each alternative.

Listing 4 and Listing 5 together show a concrete example of how this works, in the form of the classes used to represent and verify a SymmetricBinding assertion. The SymmetricBinding class shown in Listing 4 is pretty simple, just defining an inner class based on the BindingAssertionHandler class shown in Listing 5 for the verification handling:

Listing 4. SymmetricBinding
public class SymmetricBinding extends NestedPolicy {
    public VerificationHandler createHandler() {
        return new AssertionHandler();
    }
    
    private static class AssertionHandler extends BindingAssertionHandler
    {
        /** Containing element name. */
        private static final String ELEMENT_NAME = "SymmetricBinding";
        
        /** Names of allowed token role elements. */
        private static final Set<String> TOKEN_ROLES =
            VerificationHandlerBase.buildNameSet("EncryptionToken|...|ProtectionToken");
        
        protected AssertionHandler() {
            super(ELEMENT_NAME, TOKEN_ROLES,
                BindingAssertionHandler.ENCRYPTION_BINDING_MARKERS);
        }

        public boolean complete(ValidationContext vctx) {
            boolean good = true;
            Map<String, TokenProperty> tokens = getRoleTokens();
            if (tokens.containsKey("ProtectionToken")) {
                if (tokens.containsKey("EncryptionToken")) {
                    vctx.reportError("sp:ProtectionToken conflicts ...", this);
                    good = false;
                }
                if (tokens.containsKey("SignatureToken")) {
                    vctx.reportError("sp:ProtectionToken conflicts ...", this);
                    good = false;
                }
            } else if (!tokens.containsKey("EncryptionToken") && 
              !tokens.containsKey("SignatureToken")) {
                vctx.reportWarning("No tokens defined for binding", this);
            }
            return super.complete(vctx) && good;
        }
    }
}

The SymmetricBinding.AssertionHandler inner class defines the set of token roles defined for the <sp:SymmetricBinding> and also implements the VerificationHandler.complete() method to check that at least one type of token is present and no conflicting tokens are present. (<sp:SymmetricBinding> allows either an <sp:ProtectionToken>, with is used for both signing and encrypting messages, or separate <sp:EncryptionToken> and/or <sp:SignatureToken>s.)

BindingAssertionHandler, shown in Listing 5, is a common base for handling verification of all three types of binding assertions (transport, asymmetric, and symmetric). These each define one or more marker assertions, one or more token roles, a required <sp:AlgorithmSuite>, and an optional <sp:Layout> (the last two being assertions with child marker assertions but no nested policy).

Listing 5. BindingAssertionHandler
public class BindingAssertionHandler extends VerificationHandlerBase {
    /** Names of marker elements allowed in &lt;TransportBinding>. */
    public static final Set<String> TRANSPORT_BINDING_MARKERS =
        VerificationHandlerUtils.buildNameSet("IncludeTimestamp");
    
    /** Names of marker elements allowed in ... or &lt;SymmetricBinding>. */
    public static final Set<String> ENCRYPTION_BINDING_MARKERS =
        VerificationHandlerUtils.
        buildNameSet("IncludeTimestamp|...|OnlySignEntireHeadersAndBody");

    
    /** Actual element name. */
    private final String m_elementName;
    
    /** Roles allowed for tokens. */
    private final Set<String> m_tokenRoles;
    
    /** Token properties for binding. */
    private final Map<String,TokenProperty> m_roleTokens;
    
    /** Marker assertions allowed in policy. */
    private final Set<String> m_knownMarkers;
    
    /** Marker token assertions. */
    private final Map<String,ExtensibleMarker> m_nameMarkers;
    ...
    protected BindingAssertionHandler(String name, Set<String> roles,
        Set<String> markers) {
        m_elementName = name;
        m_tokenRoles = roles;
        m_roleTokens = new HashMap<String,TokenProperty>();
        m_knownMarkers = markers;
        m_nameMarkers = new HashMap<String,ExtensibleMarker>();
    }
    ...
    public void addMarker(ExtensibleMarker marker, ValidationContext vctx) {
        String name = marker.getName();
        if (m_knownMarkers.contains(name)) {

            // generate warning for duplicate assertion
            VerificationHandlerUtils.checkRepeat(marker, m_nameMarkers, vctx);
        } else {
            vctx.reportError("Assertion not allowed as child of sp:" + m_elementName,
                marker);
        }
    }

    public void addGeneral(AssertionBase asser, ValidationContext vctx) {
        if (asser instanceof TokenProperty) {
            TokenProperty token = (TokenProperty)asser;
            String name = token.getName();
            if (m_tokenRoles.contains(name)) {
                TokenProperty prior = m_roleTokens.get(name);
                if (prior == null) {
                    m_roleTokens.put(name, token);
                } else {
                    vctx.reportError("Duplicate token ", asser);
                }
            } else {
                vctx.reportError("Token not allowed as child of sp:" + m_elementName,
                    asser);
            }
        } else if (asser instanceof AlgorithmSuite) {
            ...
        } else {
            vctx.reportError("Assertion not allowed as child of sp:" + m_elementName,
                asser);
        }
    }

    public boolean complete(ValidationContext vctx) {
        if (m_algorithmSuite == null) {
            vctx.reportError("Missing required sp:AlgorithmSuite property", this);
            return false;
        } else {
            return true;
        }
    }
}

Both Listing 4 and Listing 5 use a VerificationHandlerUtils.buildNameSet() to construct a set of names from a string value. This method breaks the input string at | (pipe) characters to find the individual names to be added to the set, resulting in much more concise code than if the names were passed individually. The sets of names are then used to check assertions allowed in the nested policy.

Unmarshalling the model

The use of multiple namespaces with basically the same data creates some major problems for XML data binding. Most data binding tools could only deal with these multiple namespaces by creating separate sets of classes for each namespace. JiBX data binding can do better, by using multiple bindings to the same classes. Each binding can use the same element names for each class, but in a different namespace.

The loose structure of WS-Policy and WS-SecurityPolicy also causes problems for data binding, but here again JiBX can cleanly handle the data with a little added effort. JiBX supports user extension code for unmarshalling (and marshalling) data structures that otherwise cannot be bound to XML. I used several custom unmarshallers to handle the WS-Policy and WS-SecurityPolicy data. Probably the most interesting one is OperatorUnmarshaller, used to unmarshal any of the WS-Policy operators and their child assertions. Listing 6 shows the code for this unmarshaller:

Listing 6. OperatorUnmarshaller
public class OperatorUnmarshaller implements IUnmarshaller, IAliasable {
    ...
    public boolean isPresent(IUnmarshallingContext ictx) throws JiBXException {
        UnmarshallingContext ctx = (UnmarshallingContext)ictx;
        ctx.toTag();
        if (PolicyNamespace.LOOKUP.getNamespace(ctx.getElementNamespace()) != null) {
            String name = ctx.getElementName();
            return "Policy".equals(name) || "ExactlyOne".equals(name) ||
                "All".equals(name);
        }
        return false;
    }
    
    public Object unmarshal(Object obj, IUnmarshallingContext ictx) ... {
        if (isPresent(ictx)) {
            return unmarshalOperator((UnmarshallingContext)ictx);
        } else {
            return null;
        }
    }

    private OperatorBase unmarshalOperator(UnmarshallingContext ctx) ... {
        
        // create the instance to be returned
        NamespaceInfo ns = PolicyNamespace.LOOKUP.
            getNamespace(ctx.getElementNamespace());
        if (ns == null) {
            throw new IllegalStateException("Internal error - ...");
        }
        Policy policy = Policy.getNestedPolicy(ctx);
        PolicyNamespace prior = policy == null ?
            null : (PolicyNamespace)policy.checkNamespace(ns);
        Policy policy = Policy.getNestedPolicy(ctx);
        String name = ctx.getElementName();
        OperatorBase operator;
        if ("Policy".equals(name)) {
            policy = new Policy(policy, ns);
            operator = policy;
        } else if ("ExactlyOne".equals(name)) {
            operator = new OperatorBase.ExactlyOne(ns);
        } else {
            operator = new OperatorBase.All(ns);
        }
        
        // check for namespace conflict
        if (prior != null && ns != prior) {
            ((ValidationContext)ctx.getUserContext()).reportError("Policy namespace " +
                ns.getUri() + " conflicts with containing policy namespace " +
                prior.getUri(), operator);
        }
        
        // track object and handle all attributes
        ctx.pushTrackedObject(operator);
        operator.readAttributes(ctx);
        ctx.nextToken();
        
        // process all child elements
        while (ctx.toTag() == IXMLReader.START_TAG) {
            if (isPresent(ctx)) {
                
                // unmarshal child policy operator
                operator.getChildOperators().add(unmarshalOperator(ctx));
                
            } else {
                String uri = ctx.getElementNamespace();
                name = ctx.getElementName();
                IUnmarshaller unmarshaller = ctx.getUnmarshaller(uri, name);
                if (unmarshaller == null) {
                    
                    // treat unmapped element from known namespace as marker assertion
                    ns = policy.getNamespace(uri);
                    if (ns != null) {
                        operator.getChildAssertions().add
                            (ExtensibleMarkerUnmarshaller.unmarshal(ctx, ns));
                    } else {
                        // just use DOM for element from unknown namespace
                        ...
                    }
                    
                } else {
                    
                    // unmarshal known child element as policy assertion
                    Object object = unmarshaller.unmarshal(null, ctx);
                    if (object instanceof AssertionBase) {
                        operator.getChildAssertions().add((AssertionBase)object);
                    } else {
                        throw new JiBXException("Internal error - child element ...");
                    }
                    
                }
            }
        }
        ctx.nextToken();
        ctx.popObject();

        return operator;
    }
}

The IUnmarshaller interface only defines two methods: isPresent() to check if the current element start tag is handled by the unmarshaller, and unmarshal() to unmarshal the data from an element. In the Listing 6 code, the isPresent() method just checks if the current element namespace matches one of the WS-Policy versions, and then if the element name matches any of the three policy-operator names (Policy, ExactlyOne, or All).

The unmarshal() method is also simple, but only because it delegates all the work to the unmarshalOperator() method. unmarshalOperator() assumes that you're positioned at one of the policy-operator elements, and starts by creating an instance of the matching operator class using the appropriate WS-Policy namespace (verifying that the namespace used on this operator matches that on the containing <wsp:Policy> element, if any). It then executes a loop to unmarshal all child elements. There are four ways of handling child elements:

  • If the child is another policy operator, recursively call unmarshalOperator().
  • If there's an unmarshaller for this element (meaning the binding definition contains a mapping definition for the element), call that unmarshaller.
  • If the element namespace is recognized as a policy-extension namespace, unmarshal as an empty marker assertion.
  • Otherwise, just treat as an unclassified extension element and use a DOM representation.

The third option means that marker elements don't need to be named in the JiBX binding definitions, which helps keep the bindings relatively simple (and also don't require individual classes, which keeps the data structures relatively simple). These bindings do need to define JiBX mapping definitions for all the nonmarker assertions, though, and separate bindings must be used for each namespace. Listing 7 shows the top-level binding, containing common abstract mappings (not associated with any element name, and hence reusable across namespaces) for both WS-Policy and WS-SecurityPolicy:

Listing 7. Top-level unmarshal binding definition
<binding package="com.sosnoski.ws" trim-whitespace="true"
    value-style="attribute" force-classes="true" direction="input" track-source="true">
  
  <include path="in-policy-200409.xml"/>
  <include path="in-policy-200702.xml"/>
  <include path="in-secpolicy-200507.xml"/>
  <include path="in-secpolicy-200702.xml"/>
  
  <!-- Base marker element mapping -->
  <mapping class="com.sosnoski.ws.policy.ExtensibleMarker" unmarshaller=
    "com.sosnoski.ws.secpolicy.SecurityPolicyNamespace$SecurityPolicyMarkerUnmarshaller"/>
  
  <!-- Basic nested policy mapping -->
  <mapping abstract="true" class="com.sosnoski.ws.secpolicy.NestedPolicy"
      pre-set="preset" ordered="false" allow-repeats="true">
    <structure set-method="setPolicy" usage="optional"
        unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
    <structure type="org.w3c.dom.Element" unmarshaller="org.jibx.extras.DomElementMapper"
        set-method="addExtension" usage="optional"/>
  </mapping>
  ...
  <!-- Token base mapping -->
  <mapping abstract="true" class="com.sosnoski.ws.secpolicy.TokenBase"
      ordered="false" allow-repeats="true">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/>
    <structure name="Issuer" set-method="setIssuer" usage="optional"
         unmarshaller="com.sosnoski.ws.policy.ExtensibleValueUnmarshaller"/>
    <structure name="IssuerName" set-method="setIssuerName" usage="optional"
         unmarshaller="com.sosnoski.ws.policy.ExtensibleValueUnmarshaller"/>
  </mapping>
  
  <!-- Token property base mapping -->
  <mapping abstract="true" class="com.sosnoski.ws.secpolicy.TokenProperty"
      pre-set="preset" ordered="false" allow-repeats="true">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/>
  </mapping>
  
  <!-- Base handling for protection specifications -->
  <mapping abstract="true" class="com.sosnoski.ws.secpolicy.ProtectParts"
      pre-set="preset" ordered="false" allow-repeats="true">
    <structure name="Body" set-method="setBody" usage="optional"
        unmarshaller="com.sosnoski.ws.secpolicy.SecurityMarkerUnmarshaller"/>
    <structure name="Attachments" set-method="setAttachments" usage="optional"
        unmarshaller="com.sosnoski.ws.secpolicy.SecurityMarkerUnmarshaller"/>
    <structure name="Header" set-method="addHeader" usage="optional"
        unmarshaller="com.sosnoski.ws.secpolicy.Header$HeaderUnmarshaller"/>
  </mapping>
  
</binding>

Listing 8 shows a pair of the namespace version-specific bindings referenced by <include> elements in the Listing 7 binding, one for a WS-Policy namespace and one for a WS-SecurityPolicy namespace. These associate the namespace version-independent data model classes with element names in a particular namespace, while either passing the handling on to specific unmarshaller classes (in the case of the WS-Policy operator elements, the Listing 6 OperatorUnmarshaller class) or delegating to one of the abstract mappings from the Listing 7 binding.

Listing 8. WS-Policy and WS-SecurityPolicy unmarshal binding definitions
<binding value-style="attribute" force-classes="true" direction="input" 
      track-source="true">

  <!-- Make the recommendation namespace the default -->
  <namespace uri="http://schemas.xmlsoap.org/ws/2004/09/policy"
      default="elements" prefix="wsp"/>
  
  <!-- Define all supported policy elements -->
  <mapping name="Policy" class="com.sosnoski.ws.policy.Policy"
      unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
  <mapping name="ExactlyOne" class="com.sosnoski.ws.policy.OperatorBase$ExactlyOne"
      unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
  <mapping name="All" class="com.sosnoski.ws.policy.OperatorBase$All"
      unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
  <mapping class="com.sosnoski.ws.policy.PolicyReference" name="PolicyReference">
    <structure map-as="PolicyReference"/></mapping>

</binding>

<binding value-style="attribute" force-classes="true" direction="input" 
      track-source="true">

  <!-- Make the WS-SecurityPolicy 1.1 namespace the default -->
  <namespace uri="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"
      default="elements" prefix="sp1"/>
  
  <!-- Token variations -->
  <mapping name="SecureConversationToken" 
      class="com.sosnoski.ws.secpolicy.SecureConversationToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenBase"/></mapping>
  <mapping name="X509Token" class="com.sosnoski.ws.secpolicy.X509Token">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenBase"/></mapping>
  ...
  <!-- Token property variations -->
  <mapping name="InitiatorToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$InitiatorToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="InitiatorSignatureToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$InitiatorSignatureToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="InitiatorEncryptionToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$InitiatorEncryptionToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="RecipientToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$RecipientToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="RecipientSignatureToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$RecipientSignatureToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="RecipientEncyrptionToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$RecipientEncyrptionToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="ProtectionToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$ProtectionToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="EncryptionToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$EncryptionToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  <mapping name="SignatureToken" 
      class="com.sosnoski.ws.secpolicy.TokenProperty$SignatureToken">
    <structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
  ...
  <!-- Define other assertions containing nested policies -->
  <mapping name="AlgorithmSuite" class="com.sosnoski.ws.secpolicy.AlgorithmSuite">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
  <mapping name="AsymmetricBinding" class="com.sosnoski.ws.secpolicy.AsymmetricBinding">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
  <mapping name="BootstrapPolicy" class="com.sosnoski.ws.secpolicy.BootstrapPolicy">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
  <mapping name="Layout" class="com.sosnoski.ws.secpolicy.Layout">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
  <mapping name="SymmetricBinding" class="com.sosnoski.ws.secpolicy.SymmetricBinding">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
  <mapping name="Trust13" class="com.sosnoski.ws.secpolicy.Trust13">
    <structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
  ...
  <!-- Other elements with specific handling -->
  <mapping name="SignedParts" class="com.sosnoski.ws.secpolicy.ProtectParts$SignedParts"
      factory="com.sosnoski.ws.secpolicy.ProtectParts.newSignedParts">
    <structure map-as="com.sosnoski.ws.secpolicy.ProtectParts"/></mapping>
  <mapping name="EncryptedParts" 
      class="com.sosnoski.ws.secpolicy.ProtectParts$EncryptedParts"
      factory="com.sosnoski.ws.secpolicy.ProtectParts.newEncryptedParts">
    <structure map-as="com.sosnoski.ws.secpolicy.ProtectParts"/></mapping>
  ...
</binding>

Even with JiBX, a fair amount of complexity is involved in unmarshalling WS-Policy and WS-SecurityPolicy documents. But the combination of binding definitions with user extension code for unmarshalling makes the task much easier than would be the case with data binding tools that take a more rigid approach to handling XML.


Almost done

In this article, you've learned about the issues that make WS-SecurityPolicy difficult to understand, along with some of the common mistakes made in working with WS-SecurityPolicy documents. You've also seen the basis of a Java data model for the combination of WS-Policy and WS-SecurityPolicy that supports verifying WS-SecurityPolicy documents, and learned how JiBX data binding can be used to unmarshal documents to construct the model.

The next article will wrap up the development of a tool for verifying and reformatting WSDL and WS-Policy/WS-SecurityPolicy documents. In it, you'll see more of how the model handles policy alternatives and other aspects of verification, and also how JiBX data binding is used to output documents converted to the current versions of the standards and a best-practices format.

Resources

Learn

  • W3C Web Services Policy Working Group: This group defines the WS-Policy specification, with WS-Policy 1.5 the current version. Its work is based on the WS-Policy 1.2 submission, a proposal developed by a group of interested companies. Both the WS-Policy 1.2 submission version and the official WS-Policy 1.5 recommendation are in widespread use (and at least one web services stack, Axis2, currently supports only the submission version).
  • OASIS Web Services Secure Exchange (WS-SX) TC: This organization is responsible for WS-SecurityPolicy and developed the current WS-SecurityPolicy 1.3 (which builds on and extends the earlier WS-SecurityPolicy 1.2, keeping the WS-SecurityPolicy 1.2 namespace for the common elements). Its work is based on the WS-SecurityPolicy 1.1 submission, again a proposal developed by a group of interested companies. Most applications are now using the official WS-SecurityPolicy 1.2/1.3 recommendation, though the WS-SecurityPolicy 1.1 submission version is still widely used.
  • Browse the technology bookstore for books on these and other technical topics.
  • developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.

Get products and technologies

  • Apache Neethi: Neethi is one of several open source tools for working with WS-Policy. Neethi, like most of the other policy tools available, is based on a DOM representation of XML. It doesn't directly support WS-SecurityPolicy or other WS-Policy extensions but is designed to be extended by applications to provide such support.
  • JiBX data binding: JiBX is a tool for converting between XML documents and Java data models. It offers higher performance and greater flexibility than other data binding approaches, such as JAXB, making it especially useful for difficult document structures that don't fit simple schema definitions.

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
ArticleID=647156
ArticleTitle=Java web services: Modeling and verifying WS-SecurityPolicy
publish-date=04192011