WS-Security in the Enterprise, Part 2: The Framework

This is the second article in this series about implementing and using WS-Security-based protection in enterprise environments. The first article reviewed existing setups and gaps, and proposed plans for development of a WSSE Toolkit, which is intended to address some of those gaps. This article introduces the framework of the proposed WSSE Toolkit (a subset of current project's source is also available for download), and explains a high-level mapping of WSSE features to the object-oriented realms of the Java language.

As stated in the Toolkit's requirements, one of the intentions for this project was the simplification of dealing with WS-Security in today's programming environments. This burden should be moved to the framework, with the intent of eventually delegating it to the infrastructure layer, where it actually belongs. Therefore, before reviewing the Toolkit's structure and its relationship to the WSSE standards, I would like to take a look from the consumer's side; i.e., view the Toolkit as a black box for now.

This simple example creates a new WSSE Header with a child timestamp element, and signs the timestamp using the XML Signature provider implementation, retrieved from the Toolkit's configuration file. The signature and timestamp elements in the security header are then encrypted by the configured XML Encryption provider.

The difficulty of processing WSSE information lies in the procedural nature of XML parsing, leading to stacks of unwieldy spaghetti code. The code, which implements complicated security specifications, tries to map its procedural rules onto object-oriented realms. The Toolkit's objects, representing particular WSSE elements (see the WSSE Mapping section), will isolate this tedious parsing process, creating an object-oriented wrapper on top of XML-handling code.

TheWsseHeaderToken class, provided by the Toolkit, implements several convenience wrappers to isolate complexities of the framework from its clients. However, if necessary, the Toolkit objects may be created and accessed directly from the code--for instance, when a new type of processing, not covered by the existing helpers, is desired, a corresponding processor object may be created and inserted into the header. This is demonstrated in the example below, which copies elements from one WSSE header to another using a sample processor and token.

The framework's hierarchy maps, to a large extent, to the XML blocks described in the WSSE specification (PDF). The Toolkit contains the following types of objects:

WSSE Tokens: Tokens represent XML elements in WSSE Headers, and may also be composed; i.e., include child tokens. These elements can be either created anew, provided that sufficient information has been supplied to the token, or can be wrapped around the existing XML elements (such as SAML Assertions) extracted from another header. Tokens are represented by the base classes WsToken and WsCompositeToken in the wsse.Toolkit.Tokens package.

Token References: These objects work as token "pointers," allowing the addressing and retrieval of existing security tokens. Most commonly, these references will be used as a convenience mechanism for pointing to existing XML elements and converting their contents into token objects. The base class for token references is WsTokenRef in the wsse.Toolkit.Tokens.Refs package.

Token Processors: These represent operations, which may be performed on any element in a WSSE Header, including the header itself, and the containing SOAP message. At the moment, the Toolkit is going to include only two processors, DSigProcessor and EncProcessor, which insert additional tokens into the security header and/or alter the existing tokens. However, such a behavior is by no means a requirement, and other, non-altering types of processing are possible. The ITokenProcessor interface in the wsse.Toolkit.Processors package reflects this intention by avoiding the introduction of new tokens in signatures of its methods.

Utilities: Actually, there are multiple types of utilities in the wsse.Toolkit.Utils package (configuration, XML, etc.), as well as supporting classes for SAML and user directory operations (the wsse.Toolkit.Saml and wsse.Toolkit.Directory packages). These objects are utilized by other Toolkit components and do not represent any part of WSSE Headers.

Helper Wrappers: The helpers bring together the concepts of tokens and processors by providing building blocks for security headers and enforcing the notion of token ownership and processing. Currently, the WsseHeaderToken class, in addition to its normal token interface, also defines some additional convenience methods, which instantiate and insert Toolkit's objects for processing. The clients are certainly free to work directly with the Toolkit's objects, if so desired.

It is worth mentioning here that not all of these objects are going to be available right away--rather, they will be introduced and added to the Toolkit according to the phases, laid out in the Conclusion section of part one. Implementation of those elements of WSSE Headers will be covered in later articles of this series.

Figure 1 below graphically shows the relationships between the different types of the Toolkit framework's objects and its clients.

Figure 1. Toolkit's operation

There are several important points about the Toolkit's operation that can be added for better understanding of Figure 1:

Not all tokens can be created from the existing XML elements--for instance, it does not really make sense to reuse an existing timestamp element (wsse.Toolkit.Tokens.Wss.TsToken class)--instead, a new one is created and inserted whenever needed.

Those tokens, that can be created from existing XML elements, expose a constructor that accepts an org.w3c.dom.Element XML element representing the token, and perform shallow validity verification of the passed data. In all cases, it is very important to properly and consistently maintain the org.w3c.dom.Document that is used to create new XML elements--mismatching documents will result in errors during the insertion process. The document-handling functionality lives in the base token classes, WsToken and WsCompositeToken, and in the helper class WsTokenHelper.

Although Token References are derived from the wsse.Toolkit.Tokens.WsToken class, most of them are not expected to be insertable and serve for addressing existing XML elements. However, some of the references, covered in the WSSE Mapping section below, are expected to be insertable, so the implementation allows adding this capability to references, as well.

Token References may be used to resolve tokens from different headers--this facilitates the process of adding existing tokens to new WSSE Headers. They can even refer to not-yet-existing elements, which will be inserted during earlier processing stages (like encrypting the signature element in the earlier example).

It is the responsibility of the appropriate helpers to instantiate token and processor objects, providing all of the necessary configuration and initialization, and link them together. The processors are responsible for dealing with the processing's results--for instance, inserting a newly created DSig element into the security header. The processor's interface contract merely promises that referenced tokens will be processed in some way, and does not deal with new tokens resulting from the processing.

Processing operations may be linked together--in this way, multiple security headers' entries may be first signed, and then the signature encrypted, as shown in the example code in the client-related section.

Since the Toolkit provides a rather lightweight object layer, a new set of objects, with its own context, is instantiated for processing each client, and, therefore, concurrency is not an issue. As a result, the Toolkit's classes generally do not provide synchronization locks, with the exception of the wsse.Toolkit.Utils.Xml.XmlFactory and wsse.Toolkit.Utils.Config.ConfigHelper classes, which do contain some shared functionality. However, the configured Directory and SAML providers must be thread-safe, as the same provider implementations will be accessed from all executing clients.