Java Web 服务: WS-SecurityPolicy 建模与验证

WS-SecurityPolicy 常见的错误和如何在 Java 中进行 WS-SecurityPolicy 验证与转换建模

WS-SecurityPolicy 使您能够在 web services Description Language (WSDL) 服务描述中定义安全性配置。它是一个功能强大的工具,但是它在处理 WS-SecurityPolicy 文档方面并不出色。其中断言结构必须正确才能够生效,而版本名称空间也需要保持一致。在本文中,您将了解到创建 WS-SecurityPolicy 文档时会遇到的常见错误,您也将了解如何在 Java™ 中进行 WS-Policy 和 WS-SecurityPolicy 建模以进行验证和转换。

Dennis Sosnoski, 咨询师, Sosnoski Software Associates Ltd

Author photoDennis Sosnoski 是一名咨询师和培训师,专长是基于 Java 的 XML 和 Web 服务。他有 30 多年的专业软件开发经验,最近 10 年一直致力于服务器端 XML 和 Java 技术方面的工作。Dennis 是开源 JiBX XML Data Binding 框架及相关的 JiBX/WS Web 服务框架的首席开发人员,也是 Apache Axis2 Web 服务框架的提交者。他还是 JAX-WS 2.0 和 JAXB 2.0 规范的专家组成员之一。



2011 年 8 月 01 日

关于本系列

web servicess 是企业计算中 Java 技术的重要组成部分。在本系列文章中,XML 和 web servicess 咨询师 Dennis Sosnoski 介绍了两个对于使用 web servicess 的 Java 开发人员来说非常重要的主流框架和技术。阅读本系列文章来了解这个领域的最新开发动态,并了解您可以如何使用它们来帮助您开发编程项目。

在本系列文章的前几篇文章中,您已经了解如何在 WSDL 服务描述语言中使用 WS-SecurityPolicy 来配置 WS-Security。WS-SecurityPolicy 的一个缺点是在编写一个策略时很容易出现错误,如断言位置使用不当,或者在文档中混合使用断言版本等。许多 web servicess 协议会悄悄忽略这些错误,这意味着您的策略不会按预期方式发挥作用,并且您需要花时间去分析策略才能发现问题根源。

在本文的第一部分,您将了解到为什么 WS-SecurityPolicy 很难使用,并且会了解一些常见的 WS-SecurityPolicy 错误的发生原因。第二部分将介绍一个 WS-Policy 和 WS-SecurityPolicy 数据模型,它支持文档结构的验证,然后介绍如何使用 JiBX 数据绑定来解析模型中的文档。

用 WSDL 描述 WS-Policy

名称空间使用方法

本文使用:

  • wsdl 前缀来表示 WSDL 1.1 http://schemas.xmlsoap.org/wsdl/ 名称空间
  • wsp 前缀表示 WS-Policy 提交版本所使用的 http://schemas.xmlsoap.org/ws/2004/09/policy 名称空间
  • sp 前缀表示 WS-SecurityPolicy 1.2 所使用的 http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702

Java web 服务:理解 WS-Policy” 详细介绍了 WS-Policy 的大部分内容,包括如何将策略断言附加到 WSDL 1.1 组件上。简而言之,WS-Policy 允许您将策略附加到 WSDL 服务定义中对应消息和消息组的不同位置上。WS-Policy 使用了以下 4 个级别的消息分组:

  • 消息(Message) — 策略会应用到一条特殊的消息(这个消息会在任何通过 <wsdl:message> 元素附加策略的地方使用,或者由通过某个操作定义的 input/output/fault 附加策略的特殊操作使用,这个操作定义位于 <wsdl:portType><wsdl:binding> 元素中)。
  • 操作(Operation) — 策略会应用到某个特殊操作中交换的全部消息(策略是通过 <wsdl:operation> 元素附加的,位于 <wsdl:binding><wsdl:portType> 元素中)。
  • 端点(Endpoint) — 策略会应用到某个特定服务绑定中交换的全部消息(策略是通过 <wsdl:port><wsdl:binding> 附加的),或者基于特殊的端口类型应用到全部服务绑定中(策略会附加到对应 <wsdl:portType> 上)。
  • 服务 — 策略会应用到所有端点或与服务相关联的所有操作上(策略是在 <wsdl:service> 元素中附加)。

应用到某个级别的消息分组的策略是由底层继承的,这样应用到每一条消息的实际(用 WS-Policy 词汇表示就是有效)策略就是应用到消息、操作、终端和服务层的所有策略的联合。所以策略不仅仅决定于消息本身,也受消息所使用的上下文影响。

WS-Policy 附件技术

在 “Java web 服务:理解 WS-Policy” 一文中,我介绍了将 WS-Policy 附加到 WSDL 组件的最常用方法:使用 <wsp:PolicyReference> 元素。大多数情况下,这种通过引用实现的附加方法都适用,但是这不是惟一的方法。除此之外,您还可以在与一个消息分组级别对应的任意 WSDL 元素上使用一个特殊属性 — wsp:PolicyURIs — 来实现。这个属性的值是一个应用到该元素的策略-断言清单。您还可以通过直接嵌入策略断言而将策略断言附加到一个 WSDL 元素上。

各种附加策略断言方法的选择大多数情况下是任意的。wsp:PolicyURIs 属性可以用在一些不支持根据原始 WSDL 模式来扩展元素的 WSDL 元素上 — 但是最新的 WS-I WSDL 模式允许在所有 WSDL 元素上增加元素,这样大多数 web services 工具都是支持的。使用 <wsp:PolicyReference> 元素或 wsp:PolicyURIs 属性使您能够重用策略断言,包括外部策略断言(但是并非所有 web services 工具都支持外部引用)。

另一种以外部方式附加策略的方法是使用 <wsp:PolicyAttachment> 元素。理论上,这种方法允许您将策略关联到 WSDL 之外的服务。然而,大多数 web services 工具并不支持这种附加方式,而是在 WSDL 服务描述语言中引用或直接包含策略信息。

<wsp:PolicyReference> 使能够定义提取算法,使用可选属性来提取所引用策略的值。这个提取算法为外部策略引用提供了一些安全性保证,但是它不能保护引用本身的修改。<wsp:PolicyAttachment> 则更进一步,它支持直接使用 WS-Security 来保证整个附件的安全性。

WS-SecurityPolicy 附件限制

WS-SecurityPolicy 指定了消息分组级别,其中特定类型的策略断言会被附加到一个服务描述中:

  • 安全绑定断言<sp:TransportBinding> 只支持终端级别(因为这里使用 transport 来访问终端);<sp:SymmetricBinding><sp:AsymmetricBinding> 都支持终端或操作级别
  • 支持令牌断言(所有形式的支持令牌,包括 <sp:SupportingTokens><sp:SignedSupportingTokens> 等等):终端、操作或消息级别
  • 选项断言<sp:Wss10><sp:Wss11><sp:Trust13>):支持终端级别
  • 保护断言<sp:SignedParts><sp:SignedElements><sp:EncryptedParts><sp:EncryptedElements><sp:ContentEncryptedElements><sp:RequiredElements><sp:RequiredParts>):支持消息级别

这些级别只是建议,所以 web servicess 工具应该是能够处理各种不同的附加级别的 — 但是最好在可能的情况下遵循这些建议。

上面未列出的 WS-SecurityPolicy 断言是嵌在其他断言中的,所以所包含的断言位置决定了它们的级别。


使用 WS-SecurityPolicy

WS-SecurityPolicy 使用了一种很难直接编写或修改的复杂且容易出错的结构。大多数商业 web servicess 工具都提供了一些 GUI 工具,您可以用它的一些菜单选项生成 WS-SecurityPolicy 文档。(开源 Metro 项目也提供了这种工具,它是 NetBeans 的一部分。)这类工具的输出结果与提供这个工具的 web services 栈能够很好地配合,但是它可能并非最佳实践方法,甚至可能不完全正确。如果您不使用这样的工具,而希望修改一个工具不支持的安全性配置,或者只是希望保证您的策略是正确的,那么您必须自己处理复杂的 WS-SecurityPolicy。

我本身并不太喜欢 GUI WS-SecurityPolicy 配置工具,所以在这个系列文章中,我都是直接处理策略文档的。在测试不同类型的安全性配置时,我遇到了许多的错误,它们总是在 WS-SecurityPolicy 复杂性上面临挑战,也使我认识到有很多方法可能会导致错误。我将首先介绍为什么我认为 WS-SecurityPolicy 是很复杂的,然后介绍我在开始正确的可互操作的 WS-SecurityPolicy 配置时经常遇到的问题。

WS-SecurityPolicy 结构

WS-SecurityPolicy 1.2 模式定义了约 140 种元素,几乎所有元素都是全局定义的(这意味着它们理论上都可以单独使用,而不需要嵌入到其他元素或文档中)。它们中的大多数都被定义为空的标记元素,包含了其他名称空间支持的扩展属性,但不包含内容。而其他元素则同时支持扩展属性和其他名称空间的元素,但是不支持 WS-SecurityPolicy 名称空间的元素。WS-SecurityPolicy 1.3 模式是 1.2 版本的扩展,增加了一些新的元素。

不管是 1.2 版本模式还是 1.3 版本的扩展模式在实践中都不太实用。这并非是模式作者的错误,而是由 XML 模式本身的限制和 WS-Policy 的结构造成的。

正如在 “Java web 服务:理解 WS-Policy” 所介绍的,WS-Policy 提供了一个简单的定义和组合策略断言的框架,而断言本身则由诸如 WS-SecurityPolicy 的其他扩展定义,其中每一个扩展都有其自己的名称空间。清单 1 是一个 WS-SecurityPolicy 示例文档,其中粗体显示的是 WS-Policy 元素:

清单 1. WS-SecurityPolicy 示例
<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>

草率的 sop

WS-SecurityPolicy 模式定义实际上是一个定义 XML 结构的 sop。甚至当前发布的版本在其中一个元素名称上(RequireAppiesTo,实际上应该是 RequireAppliesTo)还有错误,并且在值类型上至少有一个错误(<sp:Header> 元素的 sp:Name 属性本应该是一个简单名称,但在模式中却定义为完整名称)。这些错误表示这个模式实际上并没有得到广泛使用(如果有人使用)。

清单 1 的策略文档主要由 WS-Policy 元素轮换层(在这里只有 <wsp:Policy> 元素)和 WS-SecurityPolicy 元素(使用 sp 前缀)构成。可惜,XML 模式不能表示这种结构。每一个模式定义都对应一个名称空间,虽然它可以引用和使用其他名称空间中定义的元素,但是它不能为这些元素定义子结构。所以 WS-Policy 的模式定义可以定义一个普通结构,而 WS-SecurityPolicy 模式可以定义一些准备与该结构组合使用的元素,但是现在没有办法定义这些两元素之间的交互。

由于 XML 模式无法定义文档结构,所以 WS-SecurityPolicy 标准内容中使用了与 WS-Policy 推荐相同的语法表现方式来作为文档的预期结构。清单 2 显示了标准提供的一个示例文档:

清单 2. WS-SecurityPolicy 语法示例
<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>

清单 2 中使用的语法对于大多数开发人员而言是非常容易理解的:使用圆括号来划分组,使用 | 字符表示备选项,以及使用 ? 表示可选组件。(完整的语法还使用 * 表示 0 次或多次出现,使用 + 表示 1 次或多次出现。)但是,这并非标准的 XML 语法,所以我们无法直接用它来验证实例文档。

结构错误

鉴于 WS-SecurityPolicy 的复杂性和检查文档是否符合标准的难度,很自然处理文档时遇到的最常见问题就是将断言添加到错误的位置。例如,如果您将 <EncryptedParts> 元素(清单 1 末尾)移动到上述 <SymmetricBinding> 元素的 Policy 内部时,您就得到一个不符合 WS-SecurityPolicy 标准的结构。对于这种结构错误的策略进行解析的结果是不确定的,并且是由具体的 web services 实现决定的 — 它可能会正常解析,但是很可能无法兼容其他的实现技术。

web services 实现技术可能会或不会向用户报告结构错误。在本系列文章中介绍的三种开源 web services 实现技术(Apache Axis2 1.5.4、 Metro 2.1 和 Apache CXF 2.3.3)的最新版本中测试上述 EncryptedParts 元素位置错误的例子时,只有 Metro 报告了错误和拒绝操作。Axis2 和 CXF 都接受了这个策略而执行任何错误,但是运行后不会加密消息体。这种不报告错误的问题使我们很难发现由于策略结构错误导致的问题。

版本冲突

名称空间是最容易出现 WS-Policy 和 WS-SecurityPolicy 问题的地方。WS-Policy 和 WS-SecurityPolicy 的标准化已经很多年了,但是这些技术的早期版本在标准发布之前已经被广泛应用了。虽然大多数官方标准都兼容早期版本的 XML 结构,但是会使用不同的名称空间,所以某个特定文档应该应用哪些规则集是很明显的。

大多数工具都支持官方推荐和早期版本的 WS-Policy 和 WS-SecurityPolicy,从而使这些版本能够互相兼容。(惟一例外的是 Axis2,它只支持 WS-Policy 的最新发布的 1.5.4 的提交版本。)尽管具有这样的灵活性,但是在一个文档中使用不同的名称空间还是可能会出现问题。在实践中,我们是不需要混合使用 WS-Policy 或 WS-SecurityPolicy 名称空间的,但是当您为了创建一个新的策略而组合几个策略的某些部分时,就很容易出现这种意外错误。所产生的冲突可能包括元素所使用的名称空间和使用令牌的值中的实际内容值。例如,WS-SecurityPolicy 1.2 和 1.3 都使用 http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Always 来表示消息中总是应该包含一个令牌。而旧 1.1 版本则使用 http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Always 来实现同样的功能。

因为在标准的各个版本中这些 XML 元素的含义一般不会发生变化,个别工具可能会在处理策略文档时选择忽略名称空间差别。采用这个方法的工具很可能会接受这些混合名称空间的策略而不报告任何错误。但是,通常最好要遵守官方推荐的名称空间,特别是要避免混合使用旧的名称空间和推荐的名称空间。


WS-Policy 和 WS-SecurityPolicy 建模

在 Java 语言中进行 WS-Policy 和 WS-SecurityPolicy 建模相对于在 “Java Web 服务:WSDL 1.1 理解与建模” 中讨论的 WSDL 1.1 建模而言是完全不同的挑战。然而,WSDL 建模的主要问题是它允许许多常用元素的排序变化,WS-Policy 和 WS-SecurityPolicy 会完全忽略元素的顺序,而提供一种在上层应用了许多规则的宽松结构。这意味着一个 <wsp:Policy> 元素(或任何其他的 WS-Policy 操作符变化)的预期内容是完全取决于元素的上下文。清单 1 演示了这一点,它文档里面几乎全部 15 个 <wsp:Policy> 元素都具有不同的内容模型。

数据模型

WS-SecurityPolicy 断言主要分成两类:标记器断言(空元素),它们的出现只是为了表示某些特性或功能;或者结构化断言,它嵌入了一个包含其他断言的策略。当然,结构化断言正是造成复杂性的原因所在。

清单 3 所示代码是处理基于模型的 NestedPolicy 基类和相关的 VerificationHandler 接口实现的结构化断言:

清单 3. NestedPolicyVerificationHandler
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);
}

所有结构化断言都扩展了 NestedPolicy 类,实现了 createHandler() 方法,它返回一个根据具体断言结构调整的 VerificationHandler 接口实例。VerificationHandler 提供了用于累加和验证嵌套策略中包含的断言的方法,这就是结构化断言处理的实现细节所在。当出现策略替代时,NestedPolicy.verify() 方法会为每一个替代选择创建一个独立的 VerificationHandler 实例。

清单 4 和 清单 5 一起演示了一个具体的结构化断言处理例子,以类的形式来表示和验证一个 SymmetricBinding 断言。清单 4 所示的 SymmetricBinding 类是非常简单的,它只是基于 清单 5 所示的 BindingAssertionHandler 类定义了一个内联类来处理验证过程:

清单 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;
        }
    }
}

SymmetricBinding.AssertionHandler 内联类定义了一组为 <sp:SymmetricBinding> 定义的令牌角色,也实现了 VerificationHandler.complete() 方法来检查至少有一种令牌出现,并且这些令牌不存在冲突。(<sp:SymmetricBinding> 支持可用于消息签名和加密的 <sp:ProtectionToken>,或者单独的 <sp:EncryptionToken> 和/或 <sp:SignatureToken>。)

如清单 5 所示,BindingAssertionHandler 是一个支持全部三种绑定断言(transport、asymmetric 和 symmetric)的通用基类。这里为每一种断言都定义了一个或多个标记器断言,一个或多个令牌角色,一个必需的 <sp:AlgorithmSuite> 和一个可选的 <sp:Layout>(最后两个断言带有子断言,但是没有嵌套策略)。

清单 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;
        }
    }
}

清单 4清单 5 都使用一个 VerificationHandlerUtils.buildNameSet() 方法创建一组字符串类型的名称集。这个方法使用 | 字符分隔输入字符串,然后查找名称,并将它添加到集合,这样的代码比单独传递名称更简洁一些。然后,这个名称集会用来检查嵌套策略允许的断言。

解组(Unmarshalling)模型

结合本质上相同的数据使用多名称空间会给 XML 数据绑定造成一些重大问题。大多数数据绑定工具只能通过为每一个名称空间创建单独的类来处理这里的多个名称空间。JiBX 数据绑定的做法更好,它会在相同的类中使用多个绑定。每一个绑定都可以为每一个类使用相同的元素名称,但是使用不同名称空间。

WS-Policy 和 WS-SecurityPolicy 的宽松结构也会给数据绑定带来问题,但是同样,JiBX 只需要增加少量工作就能够干净利落完成数据处理。JiBX 支持使用用户扩展代码来解组(和整理)那些原本无法绑定到 XML 的数据结构。我使用了一些自定义的分解器来处理 WS-Policy 和 WS-SecurityPolicy 数据。可能其中最有意思的是 OperatorUnmarshaller,它是用来解组各种 WS-Policy 操作符及其断言的。清单 6 显示了这个分解器的代码:

清单 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;
    }
}

IUnmarshaller 接口只定义了两个方法:isPresent() 是用来检查当前元素的开始标签是由解组程序处理的,而 unmarshal() 方法则是用来分解元素中的数据。在 清单 6 的代码中,isPresent() 方法只是检查当前元素的名称空间是否与某个 WS-Policy 版本相匹配,然后再检查元素名称是否与三种策略操作符名称的任意一个匹配(PolicyExactlyOneAll)。

unmarshal() 方法也很简单,但只是因为它将所有工作都委托给 unmarshalOperator() 方法处理。unmarshalOperator() 假定您找到其中一种策略操作符元素,然后使用相应的 WS-Policy 名称空间(验证该操作符所使用的名称空间与包含 <wsp:Policy> 元素的名称空间相匹配)创建一个匹配操作符类的实例。然后,它会执行一个循环来分解所有子元素。下面是 4 种处理子元素的方法:

  • 如果子元素是另一个策略操作符,那么就递归调用 unmarshalOperator() 方法。
  • 如果这个元素有一个解组程序(表示绑定定义包含了该元素的一个映射定义),那么就调用该解组程序。
  • 如果元素名称空间被认为是一种策略扩展名称空间,那么要将它分解为一个空的标记器断言。
  • 否则,将它作为一个未分类扩展元素,并使用一个 DOM 表示。

第三种方法意味着标记器元素不需要在 JiBX 绑定中定义,这有助于使绑定相对比较简单(也不需要单独的类,这有助于保持相对简单的数据结构)。但是,这些绑定一定要为所有的非标记器断言定义 JiBX mapping 定义,而且每个名称空间必须使用不同的绑定。清单 7 显示了顶级绑定,它包含了 WS-Policy 和 WS-SecurityPolicy 的常见抽象映射(与任何元素名称无关,因此可以在名称空间内重用):

清单 7. 顶级解组绑定定义
<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>

清单 8 显示了 清单 7 绑定中 <include> 元素所引用的一对与名称空间版本相关的绑定,其中一个是 WS-Policy 名称空间,另一个是 WS-SecurityPolicy 名称空间。这会将与名称空间无关的数据模型类与特定名称空间的元素名称相关联,同时将处理传递给具体的解组程序类(对于 WS-Policy 操作符元素,即 清单 6 OperatorUnmarshaller 类),或者委托给 清单 7 绑定中的一个抽象映射。

清单 8. WS-Policy 和 WS-SecurityPolicy 分解绑定定义
<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>

即使使用 JiBX,分解 WS-Policy 和 WS-SecurityPolicy 文档也是非常复杂的。但是通过组合使用绑定定义和用户扩展代码,分解任务会比使用那些采用更严格 XML 处理方法的数据绑定工具更简单些。


结束语

在本文中,您了解了使 WS-SecurityPolicy 难以理解的一些问题,以及一些处理 WS-SecurityPolicy 文档时常见的错误。此外,您还看到了支持 WS-Policy 和 WS-SecurityPolicy 的 Java 数据模型基础,它支持 WS-SecurityPolicy 文档的验证,并且了解如何使用 JiBX 数据绑定来分解文档以创建这个模型。

下一篇文章将介绍一个用于验证和重组 WSDL 和 WS-Policy/WS-SecurityPolicy 文档的工具。在这篇文章中,您将了解更多关于模型是如何处理策略替代方法与其他验证问题的,并且也将了解如何使用 JiBX 数据绑定将输出文档转换为最新版本标准和最佳实践格式。

参考资料

学习

  • The W3C web servicess Policy Working Group:这个小组定义了 WS-Policy 规范,其中最新版本是 WS-Policy 1.5。它的工作基于 WS-Policy 1.2 submission,这是一个由相关公司开发的提案标准。WS-Policy 1.2 提交版本和官方的 WS-Policy 1.5 推荐都得到了广泛的应用(至少,目前有一个 web services 实现技术 Axis2 是支持提交版本的)。
  • OASIS web servicess Secure Exchange (WS-SX) TC:这个组织负责管理 WS-SecurityPolicy 和开发最新版本的 WS-SecurityPolicy 1.3(构建和扩展了早期的 WS-SecurityPolicy 1.2,保留了常见元素的 WS-SecurityPolicy 1.2 名称空间)。它的工作基于 WS-SecurityPolicy 1.1 submission,这同样是一个相关公司开发的提案标准。现在大多数应用程序都使用官方推荐的 WS-SecurityPolicy 1.2/1.3 版本,但是 WS-SecurityPolicy 1.1 版本仍然得到广泛的应用。
  • 浏览 技术书店,查找关于各种技术主题的书籍。
  • developerWorks 中国网站 Java 技术专区:阅读关于 Java 编程的各个方面的大量文章。

获得产品和技术

  • Apache Neethi:Neethi 是其中一种用于处理 WS-Policy 的开源工具。Neethi 和大多数策略工具类似,是基于 XML 的 DOM 显示。它不直接支持 WS-SecurityPolicy 或其他的 WS-Policy 扩展,但是设计上支持通过应用程序扩展来实现。
  • JiBX data binding:JiBX 是一个在 XML 文档和 Java 数据模型之间实现转换的工具。与其他数据绑定方法相比,如 JAXB,它性能更高、灵活性更大,这使它特别适用于处理那些不符合简单模式定义的很难处理的文档结构。

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, SOA and web services
ArticleID=750045
ArticleTitle=Java Web 服务: WS-SecurityPolicy 建模与验证
publish-date=08012011