用 JMS 保护 XML 消息

(源自:https://www6.software.ibm.com/developerworks/cn/education/xml/x-secmes1/index.html

用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理

集成这些技术来改进企业应用程序

第 1 页, 总 共 11 页

文档选项

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


对本教程的评价

帮助我们改进这些内容


级别: 中级

Bilal Siddiqui , CTO, WaxSys

2005 年 12 月 15 日

Java 消息服务(Java Message Service,JMS)是一种基于 Java™ 语言的消息 API。XML 提供了一种简单、人类可读的信息交换数据格式,在企业数据格式化中是一种很受欢迎的语法。因此,在 JMS 应用程序中集成 XML 可以为企业应用程序带来很大的优势。本教程介绍如何在已有的 JMS 网络上支持对 XML 消息的保护。

开始之前

关于本系列

本系列教程包括两部分,示范了如何在企业应用程序中使用 Java Messaging Service (JMS) 保护 XML 消息。JMS 是一种基于 Java 的消息 API,是由 Java Community Process (JCP) 按照 Java Specification Request (JSR) 914 号(请参阅 参考资料)的要求开发的。

本教程的目的是通过一步一步地示范,说明如何扩展 JMS 功能来支持安全 XML 消息的编辑和处理。同时,我还将介绍和示范下列技术:

 

  1. JMS 的体系结构
  2. 客户机应用程序如何使用 JMS 功能
  3. 如何扩展 JMS 功能来支持安全 XML 消息的编辑和处理
  4. 如何使用 X.509 证书保护 XML 消息
  5. 如何在 XML 消息中使用随机数作为加密密钥
  6. 如何使用 IBM alphaWorks XML Security Suite for Java (XSS4J) 编辑和处理受保护的 XML 文档
  7. 如何在 JMS 应用程序中集成 XSS4J 来保护 XML 消息
  8. 如何开发一个基于 JMS 的示例消息应用程序,其中要用到 XSS4J 的安全特性



回页首

关于本教程

本教程是本系列的第 1 部分,介绍上述八项中的前五项。除了 XSS4J 以外(在第 2 部分介绍),第 1 部分涉及到上述所有内容。


回页首

前提条件

本教程是为 Java 程序员编写的,因此读者应该对 Java 语言有坚实的了解。(关于这个主题的更多背景资料,请访问 developerworks Java 技术新手入门。)JMS 的专门知识不是必需的,但会有帮助。

还需要对 XML 有一定了解,至少能阅读和手工编写 XML 文件。(更多背景资料请访问 developerWorks XML 新手入门。)只有当希望修改本教程中的代码以适应具体应用程序时才需要了解文档对象模型(DOM)的工作原理。但是,如果只需要理解本教程的内容,则不需要掌握 DOM。

熟悉安全的基本概念(比如私钥和公钥、签名、加密)会有帮助,但不是必需的。


回页首

我应该阅读本教程吗?

本教程没有讨论一般企业消息中使用 JMS 的全部细节。如果希望了解 JMS 的用法,请参阅下列 IBM developerWorks 参考资料:



回页首


教程内容

第 1 部分包括以下几节:

  1. 教程简介。
  2. 一个企业消息交换场景,说明了保护 XML 消息的需要。这一节还介绍了 JMS 体系结构。
  3. 示范客户机如何使用 JMS 功能。
  4. 将 XML 编辑和处理功能结合到 JMS 的策略。这一节还示范了如何实现这种策略。这一节的最后将建立一个可运行的 XML-JMS 应用程序,但是还没有受到保护。
  5. 讨论 XML 数字签名,解释消息交换场景(即第 2 节中的场景)如何使用数字签名进行身份验证。
  6. 讨论 XML 加密,说明如何在企业不同部门间的 XML 消息传递中引入保密性。
  7. 讨论小结中包含对第 2 部分内容的简要介绍。


回页首


示例代码和安装要求

本教程中的代码适用于任何兼容 JMS 1.1 的实现。我已经在 参考资料 中列出了一些 JMS 实现,可以下载下来试一试。

本教程中的代码已在下列 JMS 实现中运行通过:

 

  • Sun Java 2 Enterprise Edition Software Development Kit (J2EE SDK) version 1.4.1 中提供的参考实现。可以从 J2EE Web 站点下载 J2EE SDK。
  • OpenJMS 是一种开放源码的 JMS 实现,可以从 OpenJMS Web 站点下载。

 

要使任何 JMS 实现能够工作,都需要在计算机上安装 Java Development Kit (JDK)。本文中的例子使用了的是 JDK version 1.4.2,可以从 Sun 网站(请参阅 参考资料)下载。


回页首


用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理

集成这些技术来改进企业应用程序

第 2 页, 总 共 11 页

文档选项

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


对本教程的评价

帮助我们改进这些内容


为何在企业应用程序中需要保护 XML 消息?

企业应用程序中的 XML

XML 已经成为企业间数据交换的事实标准。实现跨企业集成应用程序的互操作性的努力(如 B2B 应用程序)主要以 XML 为基础。

本教程将 XML 用于 JMS。过去 JMS 应用程序很难越过企业的边界。JMS 通常在企业消息应用程序内部 使用。这意味着互操作性的问题与 JMS 应用程序关系不大。因此,在介绍如何将 XML 用于 JMS 之前,首先要解释什么情况下为何要在 JMS 应用程序中使用 XML。

首先来看一个企业消息应用程序,我所举的例子是企业资源规划(ERP)应用程序中的消息交换场景。部署 ERP 应用程序通常是为了管理企业资源,如产品、销售、库存、人力资源,等等。

企业的销售部门使用 ERP 消息应用程序准备报价单(或者标书),如 图 1 所示。为了准备报价单,销售部门需要从生产和财务部门获得一些信息。生产部门提供与生产有关的信息,如生产成本和预计的交货时间。财务部门提供一些细节,如需要结合到报价单中的财务条款。
图 1. 销售部门想要准备的报价单

销售部门向生产部门发送一条消息,列出了报价单中的所有项目。生产部门估计加工出所有这些产品需要多少时间。还要顾及加工的成本。

完成自己要提供的内容后,生产部门将报价单转发给财务部门。财务部门在报价单上增加财务条款,然后返回给销售部门。

图 2 描述这种消息交换场景。下一节 将详细说明其中的每一步。
图 2. ERP 应用程序的消息交换场景



回页首

ERP 应用程序场景的一个例子

  1. 销售部门提供需要在报价单中列出的项目。它将这些项目列表包装成 XML 格式,如 清单 1 所示。清单 1 中的 items 元素包括一些 item 元素,每个 item 包含报价单中每一项的细节(如 图 1 所示)。

    销售部门还对项目列表进行了数字签名,以便让消息的接收者能够验证这个 XML 消息确实是由销售部门编辑的。注意,清单 1 中没有包含数字签名的详细细节。这些内容将在稍后的 签署 XML 消息 中详细讨论。
    清单 1. 销售部门的项目列表

    
    								
      <?xml version="1.0" encoding="UTF-8"?>
      <quotation Id="0123" dated="Mon, 1 Aug 2005">
        <itemInquiryDate>Mon, 1 Aug 2005</itemInquiryDate> 
        <items>
          <item Id="item-021">
            <model>PF486</model> 
            <description>Power factor controller</description> 
            <quantity>95</quantity> 
            <unit>Nos.</unit>
          </item>
          <item Id="item-022">
            <model>KN34</model> 
            <description>Voltage controller</description> 
            <quantity>15</quantity> 
            <unit>Nos.</unit>
          </item>
          <!-- Other items -->
        </items>
        <Signature>
          <!-Details of the marketing department's signature-->
        </Signature>
      </quotation>
    

  2. 销售部门将 清单 1 中的消息发送给生产部门。
  3. 生产部门计算生产成本和交付时间,并将成本和交付时间数据增加到收到的消息中。生产部门也对新的数据进行了签署。现在的消息如 清单 2 所示。

    注意 清单 2 中的 costsdeliveryPeriods 标记。这两个标记包含成本和交付时间数据。比如,costs 标记中包含一些 cost 标记,每个 cost 标记包含某个 item 的成本数据。
    清单 2. 包含成本和交付时间数据的项目列表

    
    								
      <?xml version="1.0" encoding="UTF-8"?>
      <quotation Id="0123" dated="Mon, 1 Aug 2005">
        <itemInquiryDate>Mon, 1 Aug 2005</itemInquiryDate>
        <items>
          <item Id="item-021">
              <model>PF486</model> 
              <description>Power factor controller</description> 
              <quantity>95</quantity> 
              <unit>Nos.</unit>
          </item>
          <item Id="item-022">
              <model>KN34</model> 
              <description>Voltage controller</description>
              <quantity>15</quantity> 
              <unit>Nos.</unit>
          </item>
          <!-- Other items -->
        </items>
        <costs>
          <cost itemRef="#item-021" currency="rupees">454,567.6</cost>
          <cost itemRef="#item-022" currency="rupees">231,165.3</cost>
          <!--Other costs-->           
        </costs>
        <deliveryPeriods>
          <deliveryPeriod itemRef="#item-021" value="4" unit="weeks">
          <deliveryPeriod itemRef="#item-022" value="3" unit="weeks">
          <!--Delivery data for other items.-->           
        </deliveryPeriods>
        <Signature>
          <!-Details of the marketing department's signature.-->
        </Signature>
        <Signature>
          <!-Details of the production department's signature
            over the cost data.-->
        </Signature>
        <Signature>
          <!-Details of the production department's signature
            over the delivery period data.-->
        </Signature>
      </quotation>
    

  4. 生产部门对成本数据加密,对企业中的无关人员保密。注意,内部安全是企业必然的要求。参考资料 一节包含一些文章链接,说明了企业中内部安全的重要性。

    对于数据和信息,所谓内部安全是指仅当履行要求的工作职责需要时才对雇员公开。此外,雇员了解对所接触信息的不当处理会造成什么样的后果也很重要。因此,如果需要向对方发送文档,应该加密文档中与那个人无关的重要部分。

    现在 XML 消息如 清单 3 所示。注意 清单 2 中的 costs 元素已经被 清单 3 中的 EncryptedData 元素代替了。在后面的 保密 XML 消息 一节,我将介绍 EncryptedData 元素的全部细节。现在只要知道只有财务部门才能解密这些加密的成本数据就够了。
    清单 3. 生产部门对成本数据加密后的 XML 消息

    
    								
      <?xml version="1.0" encoding="UTF-8"?>
      <quotation Id="0123" dated="Mon, 1 Aug 2005">
        <itemInquiryDate>Mon, 1 Aug 2005</itemInquiryDate> 
        <items>
          <item Id="item-021">
            <model>PF486</model>
            <description>Power factor controller</description>
            <quantity>95</quantity> 
            <unit>Nos.</unit>
          </item>
          <item Id="item-022">
            <model>KN34</model>
            <description>Voltage controller</description>
            <quantity>15</quantity> 
            <unit>Nos.</unit>
          </item>
          <!-- Other items -->
          </items>
          <EncryptedData>
            <!--The cost data in encrypted form-->
          </EncryptedData>
          <deliveryPeriods>
            <deliveryPeriod itemRef="#item-021" value="4" unit="weeks">
            <deliveryPeriod itemRef="#item-022" value="3" unit="weeks">
            <!--Delivery data for other items.-->           
          </deliveryPeriods>
          <Signature>
            <!-Details of the marketing department's signature.-->
          </Signature>
          <Signature>
            <!-Details of the production department's signature
              over the cost data.-->
          </Signature>
          <Signature>
                over the delivery period data.-->
          </Signature>
      </quotation>
    

  5. 生产部门将 清单 3 中的 XML 消息发送到财务部门。
  6. 财务部门收到消息后,检查请求的内容,解密成本数据,确定在成本上增加的利润率,计算供给客户的价格。然后在 XML 消息中增加定价信息和财务条款。得到的消息如 清单 4 所示。注意,XML 包含 pricescommercialTerms 元素,以及财务部门的签名。
    清单 4. 财务部门增加价格和财务条款后的 XML 消息

    
    								
      <?xml version="1.0" encoding="UTF-8"?>
      <quotation Id="0123" dated="Mon, 1 Aug 2005">
        <itemInquiryDate>Mon, 1 Aug 2005</itemInquiryDate> 
        <items>
          <item Id="item-021">
            <model>PF486</model> 
            <description>Power factor controller</description> 
            <quantity>95</quantity> 
            <unit>Nos.</unit>
          </item>
          <item Id="item-022">
            <model>KN34</model> 
            <description>Voltage controller</description> 
            <quantity>15</quantity> 
            <unit>Nos.</unit>
          </item>
          <!-- Other items -->
        </items>
        <EncryptedData>
            <!--The cost data in encrypted form-->
        </EncryptedData>
        <deliveryPeriods>
            <deliveryPeriod itemRef="#item-021" value="4" unit="weeks">
            <deliveryPeriod itemRef="#item-022" value="3" unit="weeks">
            <!--Delivery data for other items.-->           
        </deliveryPeriods>
        <prices>
          <price itemRef="#item-021" currency="rupees">656,000.0</price>
          <price itemRef="#item-022" currency="rupees">431,000.0</price>
          <!--Other costs-->
        </prices>
        <commercialTerms>
          <!--Details of commercial terms.-->
        </commercialTerms>
        <Signature>
            <!-Details of the marketing department's signature.-->
        </Signature>
        <Signature>
            <!-Details of the production department's signature
              over the cost data.-->
        </Signature>
        <Signature>
            <!-Details of the production department's signature
              over the delivery period data.-->
        </Signature>
        <Signature>
            <!-Details of the commercial department's signature
              over the pricing data.-->
        </Signature>
        <Signature>
            <!-Details of the commercial department's signature
              over the commercial terms.-->
        </Signature>
      </quotation>
    

  7. 清单 4 是销售部门所需要的完整的报价单。于是,财务部门将报价单发送给销售部门。

 

后面的两节(ERP 应用程序的消息体系结构为何在 ERP 消息应用程序中使用 XML?)讨论为何一种消息体系结构适合这种 ERP 场景,以及为何 XML 是最适合这种数据交换应用程序的数据格式。


回页首

ERP 应用程序的消息体系结构

我们已经看到了企业中执行不同任务的三个部门。不需要人的干预,软件模块可以完成某些任务。比如,在 图 2 的第 3 步中,交付时间模块可以检查当前的库存状态和生产计划,看看某项任务能够多快完成。

另一方面,某些任务可能涉及到人员交互,比如企业各部门经理的干预。比方说,为了确定对某一客户的财务条款,财务部门可能需要考虑公司与该客户的关系。因此,图 2 中所示的消息交换在实现时需要一点灵活性,主要有:

  • 不能假设某项任务会立即执行,或者某项请求会立即得到响应。需要人员交互的任务可能要花很长时间。
  • 参与这类交互的软件模块不应该互相紧密耦合。这意味着如果某个模块没有立即反应(可能是因为等待某个人的交互),那么其他模块应执行自己的任务而不是傻傻地等待。

这些问题就突出表明了需要某种类似消息的异步解决方案。通常的客户机-服务器模型不适合这种应用程序,因为它假定服务提供者(或者服务器)总是能够马上提供服务。

设想一下如果 Web 服务器在服务浏览器请求之前需要人的干预会出现什么情况。这样的 Web 网站就很难运行。

对于需要人员干预的应用程序,客户机-服务器模型是不合适的,而是需要一种消息模型。这两种模型最明显的区别是响应机制。在 Internet 通常使用的客户机-服务器模型中,客户机(如浏览器)发送请求并等待服务器的响应。此时客户机一般处于闲置状态。这种通信称为同步阻塞 通信。

另一方面,消息本质上是异步的非阻塞的:客户机互相发送消息,不等待响应。在发送请求消息和收到响应之间,客户机可以随便执行其他任务。

在消息模型中,没有服务器这样的东西。所有的通信方都是消息客户机。有些客户机是消息发送者(或生产者),另一些是消息接收者(或消费者)。

在消息模型中,消息传递提供程序 起着重要的作用。它是支持消息传递所必需的中间件。消息发送者将消息发送到存在于提供程序中的接收站。提供程序保证消息存储在接收站,直到接收者有时间接收消息。

消息发送者将消息发送到消息传递提供程序,然后就开始做其他工作了。消息提供程序代表目标接收方(消息接收者)收集发来的消息。有时间的时候消息接收者再接收消息。收到消息后,可以立刻对消息响应,也可以不立即响应。等到有时间的时候再处理消息。

因此,提供程序的主要功能是把消息客户机从立即处理所收到请求的要求中解脱出来。

事实上,消息模型非常灵活,即使消息客户机离线也能工作。提供程序继续代表客户机把消息保存到每个客户机的指定位置。

这种灵活性正是用消息模型实现 图 2 所示 ERP 消息交换的主要原因。


回页首

为何在 ERP 消息应用程序中使用 XML?

为了理解 XML 为什么适合 ERP 应用程序,我们来看看 图 1 中的报价单和 图 2 的步骤。

图 1 中可以发现不同的数据部分或段。比如,价格部分包含定价数据,交付细节部分包括交货信息,财务条款部分包含支付条款这样的财务数据。也可以将这些数据段映射到 XML 标记中的不同元素,如 清单 4 中完整的报价单所示。比如,图 1 中的价格部分映射为 清单 4 中的 prices 元素。

通过 图 2 中的步骤可以看到,报价单中的不同数据段来自企业中的不同部门。

XML 格式的可扩展性使其能够以意义明确的方式将不同数据结构保存在一起。有关 XML 标准如 DOM 和 XPath 可以帮助使用现成的 XML工具处理 XML 数据。

此外值得一提的是,基于 XML 的安全标准(如 XML Signatures 和 XML Encryption)提供完善的安全特性。我将在 签署 XML 消息XML 消息保密 两节中介绍这些安全标准。

目前的主流企业级应用程序服务器(如 WebSphere)都提供了完善的 XML 编辑和处理支持(以及安全特性)。因此,在企业消息应用程序中结合 XML 和安全并不是大问题。

由于本教程中将讨论基于 Java 的 XML 消息保护,还需要指出这三种特性(XML、安全和消息)在企业 Java 中都有相应的 API。Java API for XML Processing (JAXP) 提供了 XML 支持,Java Cryptographic Architecture (JCA) 提供了安全特性,JMS 提供了消息传递支持。这意味着用 Java 代码建立企业级的安全 XML 消息解决方案,只需要巧妙地将已有的 API 结合起来。

使用 XML 的另一个好处是能够使用 XML 标准方便地将数据和表示分离。比如,可以编写级联样式表(CSS,请参阅 参考资料)文件,从 清单 4 中所示的 XML 数据生成 图 1 所示的结果。以后如果需要改变报价单的表示(视图),则不需要改变数据格式,只需要编写新的 CSS 文件即可。

总之,XML 之所以适合于这种 ERP 消息传递场景主要有两方面的原因:

  • XML 非常丰富、灵活,能够将不同来源的数据保存到一个文件中。
  • XML 相关技术(如 DOM、XPath、XML Signatures、XML Encryption 和 CSS)都很好地实现了标准化。这意味着对数据需要做的任何处理都能找到标准。因此,有大量现成的实现可以直接部署。


回页首


JMS 体系结构:概述

JMS 使用了连接工厂(connection factory)接收站(destination) 的概念。图 3 说明了接收站和连接工厂的工作原理。
图 3. JMS 体系结构

图 3 所示,管理员在 JMS provider 上创建 JMS 连接工厂和接收站。这些对象称为托管对象(administered object)

JMS 客户机应用程序(消息发送者或接收者,如销售部门)通过 JMS provider 访问连接工厂。然后 JMS provider 把连接工厂对象的引用发送给客户机应用程序。

客户机应用程序使用连接工厂创建与提供程序的连接。然后客户机使用到提供程序的连接访问特定的接收站对象。

客户机可以使用接收站对象发送或接收消息。

接收站对象可以是队列(queue)主题(topic)。客户机应用程序使用队列进行一对一通信(一个发送者和一个接收者),使用主题进行广播通信(把消息发送给多个接收者)。

图 2 中的所有消息交换通信都是一对一的,因此使用队列。图 4图 2 的修订版,展示了部署在 JMS provider 上的消息交换场景的高层视图。
图 4. 部署在 JMS provider 上的 ERP 应用程序消息交换场景

在第 1 和第 2 步中,销售部门编辑 清单 1 中的消息并发送到生产部门的队列中。

第 3、4 和 5 步中,生产部门检查队列中收到的消息,接收销售部门的消息。然后处理消息,编辑 清单 3 所示的消息,并将其发送到财务部门的队列中。

在第 6、7 和 8 步中,财务部门从提供程序上的队列中接收消息。处理该消息并编辑 清单 4 所示最终的 XML 消息,然后发送到销售部门的队列中。

第 9 步,销售部门从队列中检索消息。

JMS API 用易于使用的编程接口包装了所有这些功能。下一节将示范连接工厂和队列的使用。

您已经对 JMS 是如何工作的有了基本了解。现在来看看客户机应用程序如何使用 JMS 功能。


回页首


用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理

集成这些技术来改进企业应用程序

第 3 页, 总 共 11 页

文档选项

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


对本教程的评价

帮助我们改进这些内容


客户机应用程序如何使用 JMS 功能

为什么在扩展之前必须要了解 JMS 的功能?

这一节解释以下三点:

  • 设计一套统称为 JMS 的消息 API 的主要目的是什么?牢牢记住最初设计 JMS API 的原因非常重要。在扩展 JMS 功能的时候,要保证继续服务于这个目标。
  • 应用程序如何使用 JMS 接口构建消息逻辑?了解这一点很重要,因为在扩展现有 JMS 功能以引入 XML 编辑和处理支持时,将尽量保证对 JMS 应用模型的影响最小。因此,消息处理应用程序使用 JMS 的方式将影响扩展现有 JMS 功能的策略。
  • JMS 中的主要接口(特别是本教程所提到的接口)的功能是什么?研究这些接口可以发现 JMS 本身支持的各种消息数据类型。本教程的主要目标是扩展数据类型以便包括安全 XML 语法。

注意,本教程的目的不是示范 JMS API 的使用或者特定的 JMS 实现。这一节主要介绍后面扩展 JMS 功能要用到的那些方面。如果读者只想学习使用 JMS,请参阅 参考资料 中列出的 developerWorks 文章和教程。

如果已经了解 JMS 的工作原理,以及 JMS 实现与 Java Naming and Directory Interface (JNDI) 实现如何协作,则可以跳过这一节。


回页首

定义 JMS API 的目的

JMS API 要实现以下主要目标:

  • 该 API 应该提供必要的接口和方法,使消息传递应用程序能够使用所有需要的消息传递特性。比方说,一个重要的特性是让提供程序临时存储特定接收站的消息,直到接收方检索消息。
  • 消息传递应用程序仅依赖于 JMS API,不使用 JMS API 中没有定义的任何方法。另一方面,JMS 实现将实现细节隐藏在 JMS API 背后,仅公开 API 中定义的方法和接口。这些需求如 图 5 所示。实际上这也是所有基于 Java 的规范的主要目标。它保证了使用 JMS API 开发的应用程序可使用任何 JMS 实现。

图 5. 隐藏实现细节的 JMS 实现和使用 JMS API 定义接口的 JMS 客户机

在扩展 JMS API 的时候,我将考虑到上述两个目标。这就是说,必须保证 XML 消息处理具备 JMS 对非 XML 消息处理已经支持的所有常用和必备特性。还要保证实现能用于任何 JMS 兼容的实现。


回页首

消息处理应用程序如何使用 JMS

现在来看看消息处理应用程序如何使用 JMS API。

在开发 JMS 应用程序时,需要遵循一定的步骤。这些步骤可能因为是否需要发送或接收消息而异。下面这些步骤是必需的,无论发送还是接收消息:

  1. 初始化 Java Naming and Directory Interface (JNDI) 上下文,搜索 JNDI 上下文中的 JMS 连接工厂。
  2. 使用连接工厂建立连接。
  3. 使用连接创建 JMS 通信会话。
  4. 查找 JMS 队列。

完成这些步骤之后,如果要从 JMS provider 接收 消息,还需要以下步骤:

  1. 创建和激活一个队列接收器。
  2. 从 JMS 对列接收消息。

如果要向 JMS 用户发送 消息,则完成公共步骤后还需要执行下列步骤:

  1. 创建和激活一个队列发送器。
  2. 编辑 JMS 消息。
  3. 发送消息。

下面逐个示范上述步骤。


回页首

初始化 JNDI 上下文和查找连接工厂

首先要初始化 JNDI 上下文。所有的 JMS 实现都绑定到一个 JNDI 上下文。JNDI 定义了在网络上查找企业资源的标准方法。使用 JMS 的时候常常要用到两种最常见的 JMS 资源:连接工厂和接收站。

初始化新的 JNDI 上下文,只需要实例化一个名为 InitialContext 的对象。

InitialContext 对象包装了与远程 JNDI 服务器的连接。JNDI 服务器提供目录服务,比如存放企业资源。所有 JMS 客户机应用程序都与 JNDI 客户机协同工作。JNDI 客户机处理与 JNDI 服务器的所有通信。

JNDI 客户机需要知道 JNDI 服务器所监听的网络地址。JMS 客户机应用程序为 JNDI 客户机提供了一个名为 “jndi.properties” 的文件。“jndi.properties” 文件包含远程 JNDI 服务器的网络地址。

我结合使用 OpenJMS 和下列 JNDI 实现测试了本教程中的代码:

  • OpenJMS 的 JNDI 实现
  • Sun JDK 1.4.2 的 JNDI 实现

本教程的 下载文件 中包含两个 jndi.properties 文件(在 x-secmes1_Source.zip 中):

  • openjms_jndi.properties —— 用于 OpenJMS JNDI 实现。
  • sun_jndi.properties —— 用于 Sun 的 JNDI 实现。

但是在使用这些属性文件之前,需要将相应文件重命名为 “jndi.properties”。

下面一行代码提供了一个新的 JNDI 上下文:


    javax.naming.InitialContext jndiContext = new InitialContext();

InitialContext 对象表示 JNDI 上下文,可用它来查找需要的连接工厂。如下所示:


    QueueConnectionFactory queueConnectionFactory = 
        (QueueConnectionFactory) jndiContext.lookup ("ConnectionFactory");
    

如上所示,可使用名为 lookup() 的方法通过 JNDI 上下文查找 JMS 资源(这里是连接工厂)。lookup() 方法只有一个参数,该参数指定要查找的连接工厂的名称。lookup() 方法返回一个 QueueConnectionFactory 对象,表示一个连接工厂。可使用该连接工厂建立与 JMS 资源的连接。

但是连接工厂或其他 JMS 资源来自何处呢?谁把它们放到 JNDI 上下文中的呢?管理员为企业创建连接工厂,并通知每个消息客户机(比如销售部门)使用哪个连接工厂。因此连接工厂被称为托管对象。

JMS 实现提供了易用的图形化管理控制台,JMS 管理员可用它来管理连接工厂、队列和主题。

您可能奇怪,这里为什么一定要使用 JNDI?为什么不使用固定的硬编码的连接工厂,而要在 JNDI上下文中搜索呢?这些问题都很重要,下一步使用连接工厂时候我再来解释。


回页首

使用连接工厂建立连接

现在我们使用连接工厂创建连接,如下所示:


    QueueConnection queueConnection = 
         queueConnectionFactory.createQueueConnection();

createQueueConnection() 方法返回的对象公开了一个名为 QueueConnection 的接口。该对象代表与 JMS 队列的连接。后面将在这个连接的基础上建立通信会话。

注意,QueueConnection 提供到 JMS 队列 的连接。队列用于一对一消息传递。发送到队列中的每个消息都有一个发送者和一个接收者。JMS 还支持消息广播,发送者向主题(而不是队列)发送消息。订阅该主题的多个接收者都将收到该消息。为了简单起见,本教程中仅使用队列,而不使用主题。

现在可以回答为何要在 JNDI 上下文中查找连接工厂而不使用硬编码的连接工厂了。注意,JMS 规范仅定义了一组接口,而没有实现类。这一步得到的 QueueConnection 对象也是一个 JMS 接口而不是类。实际上并不知道该对象的类名,也不需要知道。只要知道该对象公开了 JMS QueueConnection 接口的方法就够了。

查找连接工厂的时候,要在 lookup() 方法中指定工厂的名称。然后得到也是 JMS 接口的 QueueConnectionFactory 对象。JNDI 返回的托管对象公开了 QueueConnectionFactory JMS 接口。

假设需要替换为新厂商的 JMS provider。只需要在企业 JNDI 上下文中放入新的 JMS 实现。一旦安装了新的 JMS 实现,客户机应用程序就会自动使用新的实现,因为 lookup() 方法调用将从新的 JMS 实现返回 QueueConnectionFactory 对象。

这意味着使用 JNDI 的时候,替换 JMS 实现仅仅是一项管理任务而不是编程任务。

在扩展 JMS 功能时,一定要记住这一点。必须使用和扩展现有 JMS 实现的功能,不能使用任何类名,而必须依赖 JMS 接口。这样可以保证您的类能够用于任何兼容 JMS 的提供程序。

注意,现在仅仅创建了 Connection 对象。还没有激活到 JMS provider 的连接。找到需要连接的所有 JMS 队列(一个或多个)之后再激活连接。


回页首

创建 JMS 通信会话和查找 JMS 队列

接下来要在上一步创建的连接上建立 JMS 通信会话,如下所示:


    
    QueueSession queueSession = 
      queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
    

在后面 创建和激活队列接收器创建队列发送器和激活队列连接 两节中将看到,刚刚创建的会话对象将用于发送和接收 JMS 消息。

createQueueSession() 方法有两个参数。第一个是 boolean 类型:如果使用 true 作为该参数的值,会话就成为事务型会话。就是说,会话中将包含几个消息,每个消息都是同一事务的一部分。JMS API 提供了启动、提交或者回滚事务的方法。第二个参数指定接收器端的确认模式。如果第二个参数值为 "Session.AUTO_ACKNOWLEDGE",创建的会话对象将自动确认收到的每条消息。如果传递 "Session.CLIENT_ACKNOWLEDGE" 作为第二个参数的值,会话对象就不对收到的消息进行确认,而是让接收消息的应用程序确认收到的消息。

建立会话对象后,需要找到希望接收 JMS 消息或者发送外出消息的特定队列。比方说,如果销售部门希望接收消息,就在提供程序上需要一个代表自己的队列对象。如果销售部门希望发送外出消息,就要搜索目标接收方的队列。

JMS 队列是托管对象,因此应该使用 JNDI 搜索需要的队列:


    
       Queue incomingQueue = (Queue) jndiContext.lookup("nameOfTheQueue");
    

这样就得到了需要的队列。如果需要从该队列中接收消息,可以在该队列上创建一个队列接收器。另一方面,如果查找的是要向其发送消息的队列,就需要创建一个队列发送器。下面介绍如何完成这些任务。


回页首

创建和激活队列接收器

如果希望接收消息,需要对找到的队列对象创建一个消息或队列接收器(QueueReceiver 对象)。队列接收器监听队列中收到的消息。

创建队列接收器,需要上一步中建立的 QueueSessionQueue 对象,如下所示:


    QueueReceiver queueReceiver = 
      queueSession.createReceiver ( incomingQueue );

现在有了队列接收器,可以激活它让它监听收到的消息。激活队列接收器,需要激活前面 使用连接工厂创建连接 中创建的连接:


    
    queueConnection.start();

注意,如果激活的连接上建立了多个会话,与这些会话联系的所有接收器都将被激活。


回页首

从 JMS 队列接收消息

现在可以通过队列接收器接收 JMS 消息了。只需要调用 receive() 方法,该方法属于 queueReceiver 类:


    javax.jms.Message message = queueReceiver.receive();

receive() 方法检查提供程序上队列中的消息,用 Message 对象返回消息。如果没有消息则返回 null。

JMS 支持多种消息数据类型,包括对象和文本类型的消息。JMS 支持的所有消息数据类型都从 Message 接口扩展。因此收到消息后,要检查数据类型并根据数据类型转化 Message 对象。后面 处理收到的消息 一节将示范具体的操作细节。

注意,每次要从队列中接收消息时都需要调用 queueReceiver.receive() 方法。因此最好在单独的队列接收器线程中调用该方法。接收器线程就会不断地检查队列中的消息,并在收到消息时通知客户机应用程序。

多线程编程不在本教程的讨论范围之列。一些很好的 developerWorks 参考资料 介绍了 Java 语言的多线程编程。

清单 5 展示了用单独的线程接收消息的一种简单技术。

消息接收类扩展了 Thread 类。Thread 类有一个名为 run() 的方法,消息接收类改写了这个方法。run() 方法在单独的执行线程中运行。因此,该 run() 方法包括对 queueReceiver.receive() 的调用,如 清单 5 所示。
清单 5. MessageReceiver 类


					
  public class MessageReceiver extends Thread {
    private QueueReceiver queueReceiver;
    
    public MessageReceiver () {
      try {
        InitialContext jndiContext = new InitialContext();
        QueueConnectionFactory queueConnectionFactory = 
          (QueueConnectionFactory) jndiContext.lookup ("ConnectionFactory");
        QueueConnection queueConnection =
          queueConnectionFactory.createQueueConnection();
        QueueSession queueSession = 
          queueConnection.createQueueSession (false, Session.AUTO_ACKNOWLEDGE);
        Queue incomingQueue = 
          (Queue) jndiContext.lookup ("nameOfTheTargetQueue");
        queueReceiver = 
           QueueReceiver) queueSession.createReceiver (incomingQueue);
        queueConnection.start();
      }//try
      catch ( Exception ex ) {
        ex.printStackTrace ();
      }//catch
     } 
    public void run() {
      while( true ) {
         try {
           javax.jms.Message message = queueReceiver.receive();
           if ( message != null ) {
             if ( message instanceof TextMessage ) {
                TextMessage textMessage = (TextMessage) message;
             } else if (message instanceof BytesMessage){
                //other type of JMS message receiving code
             }
           }
         }
         catch ( Exception ex ) {
           ex.printStackTrace ();
         }
      }//while(true )
    }//run()
    public static void main(String args[]) {
      //**** Step1 *****
      MessageReceiver receiver = new MessageReceiver ();
      Thread thread = new Thread (receiver);
      //**** Step2 *****
      thread.start();
    }//main()
  }    
  

run() 方法何时以及如何开始执行呢?注意 清单 5 中的 main() 方法,它说明了如何执行 MessageReceiver 类实例。

首先,main() 方法实例化 MessageReceiver 对象。MessageReceiver 类在调用 queueReceiver.receiver() 方法之前执行所有的消息接收步骤。然后 main() 方法实例化 java.lang.Thread 对象,将 MessageReceiver 对象传递给 Thread 构造函数。

最后,main() 方法通过调用 Thread 类的 start() 方法启动线程。start() 方法包含在单独线程中调用 MessageReceiver 类的 run() 方法的逻辑。MessageReceiverrun() 方法开始监听接收队列中的消息。

下面介绍队列发送器如何编辑 JMS 消息并将其发送到提供程序的队列中。


回页首

创建队列发送器和激活队列连接

JMS 消息编辑应用程序最初的步骤,与 初始化 JNDI 上下文和查找连接工厂创建 JMS 通信会话和查找 JMS 队列 中讨论的消息接收机制相同,最后得到一个 Queue 对象,如下所示:


     InitialContext jndiContxt = new InitialContext();
     QueueConnectionFactory queueConnectionFactory = 
         (QueueConnectionFactory) jndiContext.lookup ("ConnectionFactory");
     QueueConnection queueConnection =
         queueConnectionFactory.createQueueConnection();
     QueueSession queueSession = 
         queueConnection.createQueueSession (false, Session.AUTO_ACKNOWLEDGE);
     Queue targetQueue = (Queue) jndiContxt.lookup (nameOfTheTargetQueue);

获得 Queue 对象后,需要创建一个消息或队列发送器(QueueSender 对象,与 创建和激活队列接收器 中获得 Queue 对象后创建的队列接收器不同)。创建队列发送器,需要使用前面代码中建立的 QueueSessionQueue 对象:


    QueueSender queueSender = queueSession.createSender ( targetQueue );

您可能已经猜到,下一步是通过激活队列连接来激活队列发送器:


    queueConnection.start();



回页首

JMS API 支持的数据类型

现在需要编辑要通过 JMS 网络发送的消息。但是消息的编辑依赖于消息的数据类型。比方说,可能需要发送可读的文本消息供企业中的人员使用,也可能以原始字节的形式(没有文本编码)发送消息供接收端的软件模块处理。

JMS API 定义了所有消息类型的抽象结构。每个 JMS 消息都包含 header 和 body。通常,header 中包含元数据(如消息的数据类型或 MIME 类型、发送方的名称),body 中则包含消息的内容。

JMS API 定义了 Message 接口,所有消息类型都公开该接口(以及数据类型专有的接口)。Message 接口中的主要方法有 header 和 body 字段的 getter 和 setter 方法。

JMS API 定义了几种消息类型的接口。所有的消息接口都扩展自 Message 接口。一般而言,JMS 消息类型的区别在于 body 中包含的内容。JMS 支持以下不同的消息类型:

  • BytesMessage 是最基本的 JMS 消息类型,携带原始字节数组形式的消息。BytesMessage 对象也可以包含 Java 基本类型,如 intfloat
  • ObjectMessage 包含 Java 类实例。如果 JMS 用户希望向另一个 JMS 用户发送 Java 对象(以序列化的形式),就要用到该数据类型。
  • MapMessage 包含名-值对数组。名-值对中的值只能是基本数据类型,如 int
  • StreamMessage 携带 Java 基本类型流。StreamMessageBytesMessage 类型类似,都携带基本 Java 类型。但是 StreamMessage 专门用于基于流的消息传递。
  • TextMessage 携带可读的文本数据。这些数据可以是用 ASCII 等文本编码的简单文本文件。扩展 JMS 功能时将使用这种数据类型包装 XML 消息。


回页首


如何编辑 JMS 文本消息

现在介绍如何使用 JMS API 编辑文本消息。后面的 XML-JMS 队列发送器客户机 中将把这一方法扩展到编辑 XML 消息。

无论创建什么类型的消息,总要使用 创建 JMS 通信会话和查找 JMS 队列 中建立的 JMS 会话对象来实例化消息。 该会话接口包含创建所有支持的消息类型的方法。比方说要实例化文本消息,可以调用 QueueSession 对象的 createTextMessage() 方法,如下所示:


    
    TextMessage textMsg = sendSession.createTextMessage ();
    

这是一个空白的文本消息,可以像下面一样设置一些文本:


    
    String messageText = "The contents of my message....";
    textMsg.setText (messageText);
    

此外,可能还需要在 header 中设置一些消息属性。比如设置发送方的名称。Message 接口允许定义自己的属性,并在消息中设置每个属性的值。如果希望定义名为 "SenderName" 的属性和值 "Marketing department",可使用下面的方法:


    
     textMsg.setStringProperty("SenderName", "Marketing department");
    

下面介绍如何通过 JMS 队列发送 JMS 消息。


回页首

发送 JMS 消息

现在已经编写了 JMS 消息,下一步就是把消息发送到目标队列。您可能还记得,发送 JMS 消息需要前面 创建队列发送器和激活队列连接 一节获得的 JMS 队列发送器(QueueSender 对象)。

QueueSender 对象的 send() 方法将消息发送到目标队列:


    
    queueSender.send (textMsg);
    



回页首

处理收到的消息

从 JMS 队列接收消息 一节所述,队列接收器应用程序使用 QueueReceiver.receive() 方法接收 JMS 消息。接收消息的时候,接收器应用程序检查收到的消息类型然后根据数据类型进行处理。

可以使用 instanceof 运算符检查消息的数据类型:


    
    if (message instanceof TextMessage) {
       // processing code for TextMessage
    } else if (message instanceof BytesMessage){
       // processing code for BytesMessage
    } 
    //code for other types of messages
    

从上面的代码可以看到,需要对收到的消息逐个检查其 JMS 数据类型。只有当收到的消息与特定数据类型匹配时,instanceof 运算符才返回 true。

现在介绍如何处理文本消息:


    
    if (message instanceof TextMessage) {
        TextMessage textMessage = (TextMessage) message;
        String messageReceived = textMessage.getText();
    } else if (message instanceof BytesMessage) {
        // processing code for BytesMessage
    } 
    // code for other types of messages 
    

可以看到,首先要将消息转化成 TextMessage 对象。然后调用 TextMessage 接口的各种方法。

您可能已经猜到,TextMessage 接口包含便于处理文本数据的方法。比如 TextMessage.getText() 方法返回包含在 Message 对象中的文本字符串。

我开发了两个示例应用程序,MessageSender.javaMessageReceiver.javaMessageSender.java 示范了这一节中介绍的消息编辑和发送功能。MessageReceiver.java 包括接收和处理消息的逻辑。这两个示例应用程序包含在 Section3.zip 中,可以在 下载 的 x-secmes1_Source.zip 文件中找到。

下一节将说明如何设定 XML-JMS 实现的开发目标,然后讨论扩展 JMS 在 XML-JMS 中引入 XML 消息支持的策略。


回页首


用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理

集成这些技术来改进企业应用程序

第 4 页, 总 共 11 页

文档选项

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


对本教程的评价

帮助我们改进这些内容


扩展 JMS 以包含 XML 消息支持

扩展 JMS 的目标

这一节示范如何扩展 JMS 功能以包括对安全 XML 消息传递的支持。

首先我们将这种实现称之为 XML-JMS。在着手构造 XML-JMS 时要记住以下目标:

  • XML-JMS 实现应提供 JMS1.1 规范中的全部功能。
  • 不需要重新实现已有的 JMS 功能,已经有了很多现成的实现。因此,对已有的消息传递功能 XML-JMS 应该使用其他 JMS 实现。
  • XML-JMS 应该能够被任何 JMS 兼容的提供程序使用。
  • XML-JMS 用户应该能够切换他们的 JMS provider。更换提供程序是一项纯粹的管理任务(包括提供程序的安装和配置),不涉及任何编程任务(如编写额外的代码或重新编译)。
  • 为 XML-JMS 编写的客户机应用程序应该使用 JMS API,就像使用任何 JMS 实现一样。需要增加的代码仅与 XML 有关。


回页首


JMS 如何与 JNDI 结合?

讨论 XML-JMS 的体系结构之前,需要说明 JMS 如何使用 JNDI 的功能。图 6 说明了 JMS 与 JNDI 的协作。
图 6. JMS 实现与 JNDI 实现的关系

图 6 中的实线表示实际的通信,虚线表示通过这种安排实现的消息交换。

JNDI 为 JMS 提供了两种重要服务:

  • JNDI 服务器为 JMS 客户机提供了查找或搜索服务,来找到特定的连接工厂或队列。
  • JNDI 服务器监听来自客户机的消息,收到消息时通知 JMS。JMS provider 实现不一定要监听进来的消息。

通常 JMS 都提供自己的 JNDI 实现。有些 JMS 实现允许用户选择是否使用 JNDI。比如 OpenJMS 有自己的 JNDI,并允许用户选择其他 JNDI。

JMS 管理员需要创建托管的 JMS 对象。JMS 实现与管理控制台集成,让管理员创建托管对象并放到 JNDI 服务器上。

还要注意,JNDI 是一种接口或者 API,而不是通信协议。因此,JDNI 实现不一定具有互操作性。因此,如果在提供程序端改变 JNDI 实现,一定要相应地修改客户端 JNDI 实现。


回页首

XML-JMS 体系结构

现在来看看 XML-JMS 体系结构。图 7 说明了如何构建 XML-JMS。
图 7. XML-JMS 体系结构

图 7 基本上是 图 6 的扩展。图 7 中只增加了组件 XML-JMS。XML-JMS 同时需要 JNDI 服务器(标记为 "XML-JMS JNDI server")和 JNDI 客户机(标记为 "XML-JMS JNDI client")。XML-JMS 使用 JNDI 服务器与客户机应用程序(如销售部门)对话,使用 JNDI 客户机与 JMS provider (标记为 "JMS provider")对话。

注意,XML-JMS(及其 JNDI 客户机和服务器实现)与 JMS provider 可能存在于同一台机器上(如企业服务器)。JMS 客户机应用程序通过网络连接到 XML-JMS。

JMS provider 隐藏在 XML-JMS 背后。JMS 客户机不需要知道 JMS provider 的存在。

XML-JMS 对现有的 JMS 功能使用 JMS provider。因此,只需要建立 XML-JMS 中新增的功能(比如提供 XML 编辑和处理支持)。


回页首

实现 XML-JMS

要牢牢记住 图 7,实现 XML-JMS 需要经过以下步骤:

  1. 实现自己的 XML 连接工厂,JMS 应用程序的 JNDI 客户机在收到 JMS 客户机的请求时可以实例化该连接工厂。XML 连接工厂创建的连接对象是 XML 感知的。连接工厂还管理自己到 JMS provider 的连接。
  2. 将 XML 链接工厂放到 XML-JMS 服务器上,以便 JMS 客户机能够查找您的 XML 连接工厂。
  3. 实现 XML 感知的连接和会话对象。
  4. 实现 XML 感知的队列接收器。队列接收器包含用于创建和处理 XML 消息、使用 JMS provider 处理非 XML 消息的代码。
  5. 实现自己的 XMLMessage 类,帮助客户机(如销售部门)编辑和处理 XML 消息。

现在说明如何执行上述步骤。 构建 XML-JMS 时必须记住一点:将要对现有的 JMS 功能使用 JMS provider,因此只需要在 XML-JMS 中建立对 XML 处理和编辑的支持。


回页首

实现 XML 感知的 JMS 连接工厂

清单 6 说明了如何实现 XML-JMS 连接工厂。
清单 6. XMLConnectionFactory 类


					    
    public class XMLQueueConnectionFactory 
        implements QueueConnectionFactory, Referenceable { 
        private QueueConnectionFactory jMSConnectionFactory;
        private String nameOfJMSConnectionFactory = "ConnectionFactory";
        private InitialContext jndiContxt;
    
        public XMLQueueConnectionFactory () {
           try {
               jndiContxt = new InitialContext();
               jMSConnectionFactory = (QueueConnectionFactory) 
                   jndiContxt.lookup (nameOfJMSConnectionFactory);
           } catch (Exception e) {
            System.err.println("JMS provider connection failed");
               e.printStackTrace();
           }
        }
        
        public QueueConnection createQueueConnection()
            throws JMSException 
        {
            return new 
             XMLQueueConnection(jMSConnectionFactory.createQueueConnection());
        }
        public QueueConnection createQueueConnection(String s, String s1)
            throws JMSException
        {
            return new 
            XMLQueueConnection(jMSConnectionFactory.createQueueConnection(s, s1));
        }
        public Connection createConnection()
            throws JMSException
        {
            return jMSConnectionFactory.createConnection();
        }
        public Connection createConnection(String s, String s1)
            throws JMSException
        {
            return jMSConnectionFactory.createConnection(s, s1);
        }
        public Reference getReference() {
            return new Reference(
                XMLQueueConnectionFactory.class.getName(),
                new StringRefAddr("XMLFactory", ""),
                XMLQueueConnectionFactoryBuilder.class.getName(),
                null);
        }
    }//XMLQueueConnectionFactory
    

图 7 中的 XML-JMS JNDI 服务器实例化 XMLConnectionFactory 类。XMLConnectionFactory 构造函数在 JMS provider 上查找相关的连接工厂。我称之为 JMS 连接工厂。从 清单 6 中展示的 XMLConnectionFactory 构造函数,可以看到 JMS 连接工厂保存在名为 jMSConnectionFactory 的类级变量中。后面将使用它建立到 JMS provider 的连接。

还要注意,清单 6 中的 XMLConnectionFactory 类包含另一个名为 createQueueConnection() 的方法。该方法建立到 JMS provider 的连接,实例化 XMLQueueConnection 对象(下一节介绍),把和 JMS provider 的连接传递给 XMLQueueConnection 构造函数,把 XMLQueueConnection 对象返回给调用的应用程序。

客户机应用程序(如销售部门)在 XML-JMS JNDI 服务器上查找 XML 连接工厂。XML-JMS JNDI 服务器把 XMLConnectionFactory 对象返回给客户机应用程序。客户机应用程序调用 XMLConnectionFactory 对象的 createQueueConnection() 方法,它把 XMLQueueConnection 对象返回给客户机应用程序。后面的 XML-JMS 队列发送器客户机 一节将说明客户机应用程序如何完成这些工作。

现在来看看 XMLQueueConnection 对象的工作原理。


回页首

实现 XML 感知的 JMS 连接类

XMLQueueConnection 类如 清单 7 所示。
清单 7. XMLQueueConnection 类


					
    public class XMLQueueConnection implements QueueConnection {
        private QueueConnection connectionWithTheJMSProvider;
   
        public XMLQueueConnection (QueueConnection qc) {
            connectionWithTheJMSProvider = qc;
        }
    
        public QueueSession createQueueSession (
            boolean transacted, 
              int acknowledgeMode
              ) throws JMSException
        {
            return new XMLQueueSession(
                 connectionWithTheJMSProvider.
                   createQueueSession(transacted, acknowledgeMode));
        }
        //Closes the connection.
        public void close() throws JMSException{
            connectionWithTheJMSProvider.close();
        }
        //Other methods of the XMLQueueConnection class
    }
    

上一节中提到,XMLConnectionFactory 类的 createQueueConnectionFactory() 方法实例化 XMLQueueConnection 类,并在实例化 XMLQueueConnection 对象时传递 QueueConnection 对象。QueueConnection 对象表示与 JMS provider 的连接。

现在来看看 清单 7 中的 XMLQueueConnection 构造函数。XMLQueueConnection 构造函数仅仅把 QueueConnection 对象保存在类级变量 connectionWithTheJMSProvider 中。该连接对象供 XMLQueueConnection 类的其他方法使用。比如 清单 7 中所示的 createQueueSession() 方法使用该连接对象来实例化 XMLQueueSession 对象,它是 实现 XML 感知的 JMS 会话类 一节中将要讨论的 XML-JMS 的一部分。

清单 7 中的 createQueueSession() 方法调用 connectionWithTheJMSProvider 对象的 createQueueSession() 方法,该方法返回一个 QueueSession 对象。这个 QueueSession 对象表示与 JMS provider 的一个通信会话。清单 7 中的 createQueueSession() 方法把 QueueSession 对象传递给 XMLQueueSession 构造函数。

XMLQueueSession 类使用这个 QueueSession 对象创建队列发送器和接收器。

还要注意,XMLQueueConnection 类实现了 JMS 的 QueueConnection 接口。QueueConnection 接口包含多个方法,必须在 XMLQueueConnection 类中实现。只需要关注与创建 XML 连接有关的方法。因此,其他方法只需要调用 connectionWithTheJMSProvider 对象中的相关方法。这意味着只需要增加编辑和处理 XML 消息需要的功能。其他功能都来自希望用作 JMS provider 的 JMS 实现。

比如 清单 7 中的 close() 方法。它仅仅调用了 connectionWithTheJMSProvider.close() 方法。因此不需要列出 清单 7XMLQueueConnection 类的所有方法。可以在 下载 的 x-secmes1_Source.zip 中的 section4.zip 文件找到 XMLQueueConnection 类的所有方法。

其他 XML-JMS 类同样如此。我们仅讨论与 XML 有关的功能,把一般的 JMS 功能留给 JMS provider。


回页首

实现 XML 感知的 JMS 会话类

下面说明 XMLQueueSession 类的工作原理。清单 8 展示了 XMLQueueSession 类。
清单 8. XMLQueueSession 类


					
    public class XMLQueueSession implements QueueSession {
        private QueueSession sessionWithTheJMSProvider;
    
        public XMLQueueSession (QueueSession queueSession) {
            sessionWithTheJMSProvider = queueSession;
        }
        public QueueReceiver createReceiver(Queue queue) throws JMSException {
        return new XMLQueueReceiver(
            sessionWithTheJMSProvider.createReceiver(queue));
    }
    public QueueReceiver createReceiver(
        Queue queue, 
        String messageSelector) throws JMSException 
    {
            return new XMLQueueReceiver(
                sessionWithTheJMSProvider.createReceiver(
                    queue, messageSelector));
    }
        public XMLMessage createXMLMessage() throws JMSException {
            return new XMLMessage(
                sessionWithTheJMSProvider.
                    createTextMessage());
        }
    
        public XMLMessage createXMLMessage(String text) 
            throws JMSException 
        {
            return new XMLMessage(
                sessionWithTheJMSProvider.
                    createTextMessage(text));
        }
        public void close() throws JMSException {
        sessionWithTheJMSProvider.close();
        }
        //Other methods of the XMLQueueSession class
    }
    

XMLQueueSession 构造函数接收一个 QueueSession 对象参数。已经提到,在实例化 XMLQueueSession 对象时,XMLQueueConnection 类的 createQueueSession() 方法将这个 QueueSession 对象传递给 QueueSession 构造函数。这个 QueueSession 对象表示与 JMS provider 的会话。

清单 8 中的 XMLQueueSession 构造函数将 QueueSession 对象存储在名为 sessionWithTheJMSProvider 的类级变量中。

注意,XMLQueueSession 类包含几个属于 QueueSession 接口的方法需要实现。这里仅介绍与 XML 编辑处理有关的方法。其他方法仅仅调用 sessionWithTheJMSProvider 对象中的相应方法。

如何编辑 JMS 文本消息 一节中提到,QueueSession 对象包含用于创建不同 JMS 消息类型的方法。比如 createTextMessage() 方法用于创建文本消息。

因此只需要添加一个 createXMLMessage() 方法用于创建 XML 消息。

清单 8 中的 createXMLMessage() 方法调用 sessionWithTheJMSProvider 对象的 createTextMessage() 方法,该方法返回 TextMessage 对象。

createXMLMessage() 方法实例化名为 XMLMessage 的类。XMLMessage 类可以包装 XML 消息,稍后在 实现 XMLMessage 一节中介绍。createXMLMessage() 方法把 TextMessage 对象传递给 XMLMessage 构造函数,后者返回 XMLMessage 对象给客户机应用程序。

现在看看 清单 8 中的 createReceiver() 方法,它实例化 XML 感知的队列接收器的 XMLQueueReceiver 类。实例化 XMLQueueReceiver 对象时,createReceiver() 方法首先用 sessionWithTheJMSProvider 对象创建带有 JMS provider 的队列接收器,并把队列接收器传递给 XMLQueueReceiver 构造函数。下一节详细讨论如何实现 XMLQueueReceiver 类。

清单 8 中的 createSender() 方法创建并返回带有 JMS provider 的 QueueSender 对象。注意,不需要 XML 感知的队列发送器。这是因为发送任何类型的 JMS 消息都不需要任何专门的处理。

就是说 QueueSender 对象对于文本消息和 XML 消息没有任何区别。因此带有 JMS provider 的 QueueSender 对象也能发送 XML 消息。


回页首

实现 XML 感知的队列接收器

清单 8 中讨论 XMLQueueSession.createReceiver() 方法时曾经提到过 XML 感知的队列接收器,现在来看看如何实现它。

所谓的 XML 感知的 队列接收器,就是说能够区分 XML 消息和普通的非 XML 消息(比如字节消息或文本消息)。

如果是 XML 消息,队列接收器就实例化 XMLMessage 对象,把消息包装到该对象中,并将其返回给调用的应用程序。

如果是非 XML 消息,则不做任何处理直接返回给调用的应用程序(不论 BytesMessage 还是 TextMessage 对象)。

XML 感知的队列接收器类中只需要增加这种功能的实现。其他功能都使用 JMS provider。

清单 9 展示了 XMLQueueReceiver 类。receive() 方法实现了上述功能。其中没有展示 XMLQueueReceiver 类的其他方法,因为这些方法仅仅调用 JMS provider 接收器的相应方法。
清单 9. XML 感知的队列接收器


					
    public class XMLQueueReceiver implements QueueReceiver {
        private QueueReceiver receiverWithTheJMSProvider;
    
        public XMLQueueReceiver (QueueReceiver queueReceiver) {
            receiverWithTheJMSProvider = queueReceiver;
        }// XMLQueueReceiver constructor
        public Message receive()throws JMSException {
            javax.jms.Message message =
                receiverWithTheJMSProvider.receive();
            if ( message != null ) {
            if (message instanceof TextMessage) {  
                    XMLMessage xmlMsg = new XMLMessage((TextMessage) message);
                    if (xmlMsg.isXMLMessage()) {
                        return xmlMsg;
                    }
                }
                return message;
            }
            return null;
    }//receive()
       //Other methods of the XMLQueueReceiver class
    }// XMLQueueReceiver



回页首

实现 XMLMessage 类

XMLMessage 类负责 XML 消息的编辑和处理。前面的讨论中两次提到实例化 XMLMessage 对象:

  • 在讨论 清单 9 创建和返回 XMLMessage 对象时,提到过 XMLQueueSession 类的 createXMLMessage() 方法。JMS 在需要编辑 XML 消息时使用该方法。XML-JMS 队列发送器客户机 一节介绍一个示例 XML 发送器客户机时再来解释该方法。
  • 清单 9 中所示 XMLQueueReceiver 类的 receive() 方法,在接收 XML 消息时实例化一个 XMLMessage 对象。需要从 JMS 网络接收消息时,JMS 客户机调用 XMLQueueReceiver.receive() 方法。XML-JMS 队列接收器客户机 一节讨论如何实现消息接收客户机时会举一个例子。

这两处实例化 XMLMessage 对象时,都是把 TextMessage 对象传递给 XMLMessage 构造函数。清单 10 说明了 XMLMessage 构造函数如何处理 TextMessage 对象。
清单 10. TextMessage 构造函数


					
    public class XMLMessage implements TextMessage {
        private Document xmlDoc;
        private boolean XML_MESSAGE;
        private TextMessage textMessage;
        public XMLMessage(TextMessage textMessage) throws JMSException {
            this.textMessage = textMessage;
    
            if (textMessage.getText()!= null)
                loadXMLMessage(textMessage.getText());
        }//XMLMessage
        private void loadXMLMessage(String msg) {
            Document doc = createXMLDOMDocument(msg);
            if (doc == null) {
                XML_MESSAGE = false;
            } else {
                XML_MESSAGE = true;
                this.xmlDoc = doc;
            }    
        }//loadXMLMessage
        private Document createXMLDOMDocument (String xmlMsg) {
            try { 
                DocumentBuilder docBuilder = 
                    (DocumentBuilderFactory.newInstance()).
                        newDocumentBuilder();
                docBuilder.setErrorHandler(new DefaultHandler());
                InputStream is = 
                    new ByteArrayInputStream (xmlMsg.getBytes());
                Document doc = docBuilder.parse(is);
                return doc;
            } catch( Exception e ) {
                e.printStackTrace();
            }
            return null;
        }//createXMLDOMDocument 
    }//class

XMLMessage 构造函数首先将 TextMessage 对象保存到一个类级变量中。并假定 TextMessage 对象中的 XML 消息是简单文本数据。因此调用 TextMessage 接口的 getText() 方法获取文本数据。

然后调用私有 helper 方法 loadXMLMessage(),该方法使用另一个 helper 方法 createXMLDOMDocument() 将 XML 数据加载到 DOM 文档中。

createXMLDOMDocument() 方法返回与 XML 消息对应的 XML DOM 文档。XMLMessage 构造函数将 DOM 文档保存到类级变量 xmlDoc 中供将来使用。

除了上述构造函数和 helper 方法外,XMLMessage 来还包含用于 XML 编辑和处理的方法,如 清单 11 所示:
清单 11. XMLMessage 类中的 helper 方法


					
    public class XMLMessage implements TextMessage {
        //Other methods of the XMLMessage class.
        public boolean isXMLMessage() throws JMSException{
            return XML_MESSAGE;
        }//isXMLMessage
        public String getJMSType() throws JMSException{
            return MESSAGE_TYPE;
        }
        public void createId (Element element, String value) {
            Attr attr = (element.getOwnerDocument()).createAttribute("Id");
            attr.setValue(value);
            element.setAttributeNode(attr);
        }//createId
    
        public void addAttribute(Element element, String name, String value){ 
            Attr attr = (element.getOwnerDocument()).createAttribute(name);
            attr.setValue(value);
            element.setAttributeNode(attr);
        }//addAttribute
        public void addChildElement( Element parentElement, 
                                     String localName, 
                                     String namespace, 
                                     String id)
        {
            Element childElement = 
                (parentElement.getOwnerDocument()).createElementNS(namespace, localName);
            childElement.setAttributeNode(createAttribute(parentElement, "Id", id));
            
            NodeList childs = parentElement.getChildNodes();
            if (childs.getLength() > 0) {
                Element refElement;
                for (int i=0; i < childs.getLength(); i++) {
                        if(childs.item(i).getNodeType() == Node.ELEMENT_NODE) {
                        refElement = (Element) childs.item(i);
                        parentElement.insertBefore(childElement, refElement);
                        return;
                    }
                }
            }
            parentElement.appendChild(childElement);
        }//addChildElement
        public void addLastChildElement( Element parentElement, 
                                         String localName,
                                         String namespace, 
                                         String id )
        {
            Element childElement = 
                (parentElement.getOwnerDocument()).createElementNS(namespace, localName);
            childElement.setAttributeNode(createAttribute(parentElement, "Id", id));
            parentElement.appendChild(childElement);
        }//addLastChildElement
        public void addTextNode (Element parentElement, String data) {
            Text text = 
                (parentElement.getOwnerDocument()).createTextNode(data);
            parentElement.appendChild (text);
        }//addTextNode
        public void addSibling (Element siblingElement, String name, String id) {
            Element newSiblingElement = 
                (siblingElement.getOwnerDocument()).createElement(name);
            newSiblingElement.setAttributeNode(createAttribute(siblingElement, "Id", id));
            siblingElement.getParentNode().appendChild(newSiblingElement);
        }//addSibling
        public Element dereference (Element element, String attributeName) {
            String attrValue = element.getAttributeNode(attributeName).getValue();
    
            if (attrValue != null && attrValue.startsWith("#")) 
            {
                Element root = element.getOwnerDocument().getDocumentElement();
                NodeList childs = root.getChildNodes();
                
                if (childs.getLength() > 0)
                {
                    Element refElement;
                    for (int i=0; i < childs.getLength(); i++) {
                        if(childs.item(i).getNodeType() == Node.ELEMENT_NODE) {
                            String tagIdAttrValue = 
                                ((Element)childs.item(i)).getAttributeNode("Id").getValue();
                            if (tagIdAttrValue.equals(attrValue))
                               return (Element) childs.item(i);
                        }
                    }
                }//if (childs.getLength() > 0)
            }
            return null;
        }//dereference
        public Element[] findReference (Element element) {
            String elementName = element.getTagName();
            Element root = element.getOwnerDocument().getDocumentElement();
            NodeList childs = root.getChildNodes();
    
            if (childs.getLength() > 0)
            {
                Vector refElementsVector = new Vector();
                for (int i=0; i < childs.getLength(); i++) {
                    if(childs.item(i).getNodeType() == Node.ELEMENT_NODE) {
                        String uriValue = 
                            ((Element)childs.item(i)).
                                getAttributeNode("URI").getValue();
                        if (uriValue.equals(elementName)) {
                           refElementsVector.
                               addElement((Element) childs.item(i));
                        }
                        return getElementsArray(refElementsVector);
                    }
                }
            }//if (childs.getLength() > 0)
            return null;
        }//findReference()
        private Attr createAttributeNode(Element element, String name, String value){
            Attr attr = (element.getOwnerDocument()).createAttribute(name);
            attr.setValue(value);
            return attr;
        }
        private Element[] getElementsArray (Vector vector) {
            if (vector.size()>0) {
                Element[] refElements = new Element[vector.size()];
                for (int i=0; i < vector.size(); i++)
                    refElements[i] = (Element) vector.elementAt(i);
                return refElements;
            } else
                return null;        
        }//getElementsArray
        public int getJMSPriority() throws JMSException {
            return textMessage.getJMSPriority();
        }
     }//XMLMessage class
 

isXMLMessage() 公共方法判断这个 XMLMessage 对象是否包含有效的 XML 文档。如果是,isXMLMessage() 方法返回 true,否则返回 false。

getXMLDOMDocument()setXMLDOMDocument() 方法读取和设置 XML DOM 文档到 XMLMessage 类中。XMLMessage 类允许随时在 XMLMessage 类中包含新的 XML 文档。可以使用 DOM 编辑 XML 文档。然后可以使用 setXMLDOMDocument() 方法在 XMLMessage 中设置 XML 消息的 DOM 表示,该方法接收一个 DOM 文档参数。

XMLMessage 类可以提供 XML 消息的文本表示。比如,如果接收方客户机应用程序希望获得通过 JMS 网络获得的 XML 消息的文本表示,则可以调用 XMLMessage 类的 getXMLInTextForm() 方法。另一方面,如果接收方客户机应用程序希望得到 XML 消息的 DOM 表示,则可以调用 XMLMessage 类的 getXMLDOMDocument() 方法。

虽然可以使用 DOM 编辑和处理 XML 消息,为了方便起见,我还增加了几个编辑和处理方法。一般是完成特定任务的高级方法。我使用 DOM 实现了这些方法。

XMLMessage 类还包含一个 createId() 方法,用于为元素节点增加 ID。类似地,fetchElementWithId() 方法根据 ID 查找和返回一个元素。在处理 XML 消息来保护它们时再介绍这些方法的用法。

XMLMessage 类还包含一些 addXX() 方法,用于编辑 XML。比如 清单 11 中的 addChildElement() 方法有四个参数:父节点、子元素的本地名和名称空间标识符、子元素的 ID。该方法使用元素名创建一个元素,并作为第一个孩子元素添加到父元素中。孩子元素具有该标识符。

类似地,addLastChildElement() 将孩子元素作为最后一个孩子节点添加到父节点中。另一个方法 addTextNode() 在父节点中添加一个文本节点作为第一个孩子。清单 11 中的 addAttribute() 方法有三个参数:元素节点和一个名-值对,该方法为给定的元素增加属性。另一个方法 addSibling() 为指定的元素增加下一个兄弟节点,并为新建的元素创建 ID。

dereference() 方法是一个处理方法,参数包括元素和属性名。它读入属性值,将其作为片段标识符并返回片段标识符指定的元素。如果没有传递属性值则使用 URI 属性作为默认属性。

findReference() 元素与 dereference() 元素恰好相反。它接受一个元素名,返回使用片段标志符引用该元素的那些元素的数组。

您可能注意到,我仅在 XMLMessage 类中增加了少数 XML 编辑和处理方法。对于编辑和处理 XML 来说这些方法还远远不够。如果 XMLMessage 类中没有需要的简单方法,可以使用 DOM 完成有关的 XML 任务。

那么 XMLMessage 类的有效性怎么样呢?

XMLMessage 类仅仅是连接 JMS provider 的包装器。处理特定的 XML 模式时,需要扩展 XMLMessage 类,设计一套方法来提供特定模式所需要的功能。比方说,在 XMLMessage 类增加签名支持 一节和本教程下一节中,就要扩展 XMLMessage 类并设计一个 SecureXMLMessage 类,提供与安全有关的功能。

因此,可以说 XMLMessage 类仅仅帮助您把精力放到自己的 XML 模式上,而不用考虑如何把模式结合到 JMS 消息传递体系结构中。

XMLMessage 类的完整实现包含在 section4.zip 文件中,可以在 下载 的 x-secmes1_Source.zip 中找到。除了这些 XML 编辑和处理方法外,XMLMessage 类还包含很多属于 TextMessage 接口的方法。不需要重新实现 JMS 实现已经提供的功能。因此,这些方法只是调用存储在类级变量中的 TextMessage 对象的相应方法。

比如 清单 11 中的 getJMSPriority() 方法,它简单地调用 textMessage.getJMSPriority() 方法。XMLMessage 类中的其他 TextMessage 接口方法都是如此。


回页首

将 XML-JMS 放到 JMS provider 中

上面已经讨论了 XML-JMS 的实现。现在来测试 XML-JMS。为了测试 XML-JMS 的消息处理能力,我开发了两个示例程序(分别发送和接收消息)。下面两节介绍这两个程序。

但是在运行 XML-JMS 之前,首先要将 XML-JMS 放到 JMS provider 上。放置 XML-JMS 意味着要在 JMS provider 使用的 JNDI 服务器上放置 XML-JMS 连接工厂(XMLConnectionFactory 类)。OpenJMS 提供了自己的 JNDI 服务器,也允许应用程序使用其他 JNDI 服务器。我分别使用 OpenJMS 自带的 JNDI 服务器和 Sun J2EE SDK 的 JNDI 服务器进行了测试。

注意,将连接工厂放到 JNDI 服务器上时还需要告诉 JNDI 服务器以下两点:

  • 连接工厂的名称。该名称用于标识 XML-JMS 连接工厂。可使用任何名称标识连接工厂。
  • 将连接工厂放到 JNDI 服务器上时 JNDI 服务器要实例化的类。这里的类名是 XMLConnectionFactory

顾名思义,JNDI (Java Naming and Directory Interface) 是一种接口或者 API。它标准化了客户机应用程序使用连接工厂的方式,但是没有标准化放置连接工厂的图形化接口。因此,所有 JMS 服务器都有自己的放置连接工厂的图形接口。

我编写了一个简单的类 HostOnJNDIServer 将 XML 连接工厂放到 JNDI 服务器上。HostOnJNDIServer 类包含在 下载 的 x-secmes1_Source.zip 文件中。源代码还包含 readme 文件,说明如何运行 HostOnJNDIServer 应用程序将连接工厂放到 JNDI 服务器上。

注意,HostOnJNDIServer 是一个简单的 JNDI 客户机,通过程序将 Java 类放到 JNDI 服务器上。我用 OpenJMS 和 Sun 的 JNDI 服务器测试了 HostOnJNDIServer,应该也能用于其他 JNDI 服务器。

一旦安装好了连接工厂,就可以运行示例队列发送器和接收器客户机了。


回页首

XML-JMS 队列发送器客户机

清单 12 展示了名为 SampleXMLSender 的类。这个类说明了如何使用 XML-JMS 在 JMS 网络上编辑和发送 XML 消息。
清单 12. SampleXMLSender 类


					
  public class SampleXMLSender {
    public SampleXMLSender (String conFactoryName, String nameOfTheTargetQueue) 
    {
      try {            
        /*********common code**********/
        InitialContext jndiContxt = new InitialContext();
        QueueConnectionFactory queueConnectionFactory = 
            (QueueConnectionFactory) jndiContext.lookup (conFactoryName);
        QueueConnection queueConnection =
            queueConnectionFactory.createQueueConnection();
        QueueSession queueSession = 
            queueConnection.createQueueSession (false, Session.AUTO_ACKNOWLEDGE);
        Queue targetQueue = (Queue) jndiContxt.lookup (nameOfTheTargetQueue);
        QueueSender queueSender = (QueueSender) session.createSender(targetQueue);
        connection.start();
        /*********XML-specific code*********/
        //*******Step 1**********
        Document quotationDoc = createQuotationDocument();
        //*******Step 2**********
        XMLMessage xmlMessage = queueSession.createXMLMessage();
        xmlMessage.setDocumentObject(quotationDoc);
        //*******Step 3**********
        Element item = (Element)
           ((xmlMessage.getOwnerDocument().
              getRootElement()).getFirstChild());
        xmlMessage.addAttribute(item, "id", "item-021");
           //*******Step 4**********
           queueSender.send(xmlMessage);
      } catch (Exception e) {
            e.printStackTrace ();
            System.exit(-1);
      } 
      System.exit(0);
    }
    
    public Document createQuotationDocument()
    {
    Document xmlDoc = null; 
    try
    {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            xmlDoc = db.newDocument();
            Element quotation = 
                xmlDoc.createElementNS(
                      "http://www.aManufacturingEnterprise.com","quotation");
            Attr id = xmlDoc.createAttributeNS("Id", "Q-123"); 
            quotation.setAttributeNode(id);
            Element items = 
                  xmlDoc.createElementNS(
                        "http://www.aManufacturingEnterprise.com","items");
            items.appendChild( createItemElement( xmlDoc,
                                    "PF486",
                                    "Power factor controller",
                                    "95",
                                    "Nos."));
            items.appendChild( createItemElement( xmlDoc,
                                    "KN34",
                                    "Voltage controller",
                                    "15",
                                    "Nos."));
            quotation.appendChild(items);
            xmlDoc.appendChild(quotation);
 
      } catch (javax.xml.parsers.ParserConfigurationException pe) {
            pe.printStackTrace ();
            System.exit(-1);
      }
      return xmlDoc;
    }//createQuotationDocument()
    private Element createItemElement( Document xmlDoc,
                                       String modelText, 
                                       String descriptionText, 
                                       String quantityText, 
                                       String unitText)
        {
            Element item = xmlDoc.createElement("item");
            Element model = xmlDoc.createElement("model");
            Text mText = xmlDoc.createTextNode(modelText);
            model.appendChild(mText);
            item.appendChild(model);
            Element description = xmlDoc.createElement("description");
            Text dText = xmlDoc.createTextNode(descriptionText);
            description.appendChild(dText);
            item.appendChild(description);
            Element quantity =  xmlDoc.createElement("quantity");
            Text qText = xmlDoc.createTextNode(quantityText);
            quantity.appendChild(qText);
            item.appendChild(quantity);
            Element unit =  mlDoc.createElement("unit");
            Text uText = xmlDoc.createTextNode(unitText);
            unit.appendChild(uText);
            item.appendChild(unit);
            return item;
    }
    public static void main (String args[]) {
      if (args.length < 2 ) {
        System.out.println("usage: Sender <conFactoryName> <nameOfTheTargetQueue>");
        System.exit(1);
      }
      XMLMessageSender xmlSender = new XMLMessageSender(args[0], args[1]);
    }//main()
  }

清单 12 中的 main() 方法实例化 SampleXMLSender 类,该构造函数包括编辑和发送 XML 消息的逻辑。在 SampleXMLSender 构造函数中我将消息编辑和发送逻辑划分为两段代码。第一段代码标记为 “公共代码”,与 创建队列发送器和激活队列连接 一节中的相同。这些公共代码查找上一节安装的连接工厂,创建工厂的连接,然后建立会话、搜索目标队列、建立队列发送器并启动连接。

现在来看看 SampleXMLSender 构造函数的第二部分代码,标记为 “XML 专用代码”。

这些代码的第 1 步中编辑需要发送的 XML 消息。所有的 XML 编辑逻辑都放在名为 createQuotationDocument() 的 helper 方法中。该方法编辑和返回销售部门创建的 XML 消息,如 清单 12 所示。背后使用了 DOM 对象。

这里不需要讨论如何使用 DOM,可以参阅 参考资料 中与 DOM 有关的文章。还要注意,在实际的应用中,销售部门可能使用图形化的界面编辑 XML。

第 2 步创建 XMLMessage 类实例,其中包含 DOM 并将 DOM 文档传递给 XMLMessage 类的 setDocumentObject() 方法,该方法将 XMLMessage 中的 DOM 文档作为 XML 消息的内容。

实现 XMLMessage 类 一节中提到,XMLMessage 类包含编辑 XML 消息的方法。作为一个例子,我们来看看如何使用 XMLMessage 类中的 XML 编辑支持。XMLMessage 类具有双重作用,可以使用 DOM 也可使用它自己的简单 XML 编辑方法。

假设要为 XML 消息中的 Item 元素添加 Id 属性。可以使用 XMLMessage 类的 addAttribute() 方法,该方法有两个参数:目标元素和属性名-值对。

第 3 步首先获得 Item 元素,即报价单 XML 中的目标元素。然后调用 addAttribute() 方法,传递 Item 元素和需要设置的名-值对。addAttribute() 方法隐藏了 DOM 处理,为 Item 元素增加了 Id 属性。

最后,使用 “公共代码” 中创建的队列发送器对象将消息发送到目标接收方的队列中。

下面说明如何接收和处理刚刚发送的 XML 消息。


回页首

XML-JMS 队列接收器客户机

清单 13 中的 SampleXMLReceiver 类说明了如何使用 XML-JMS 接收和处理 XML 消息。
清单 13. SampleXMLReceiver 类


					
  public class SampleXMLReceiver extends Thread {
     public SampleXMLReceiver( String conFactoryName, 
                                  String nameOfTheIncomingQueue) {
       try {
         //Preparing for JMS.
         InitialContext jndiContxt = new InitialContext();
         QueueConnectionFactory queueConnectionFactory = 
            (QueueConnectionFactory) jndiContext.lookup (conFactoryName);
         QueueConnection queueConnection =
            queueConnectionFactory.createQueueConnection();
         QueueSession queueSession = 
            queueConnection.createQueueSession (false, Session.AUTO_ACKNOWLEDGE);
         Queue incomingQueue = (Queue) jndiContxt.lookup (nameOfTheIncomingQueue);
         QueueReceiver queueReceiver= session.createReceiver ( incomingQueue );
         connection.start();
       }//try
       catch ( Exception ex ) {
         ex.printStackTrace ();
       }//catch
     }//SampleXMLReceiver
    
     public void run() {
       while( true ) {
         try {
           javax.jms.Message message = receiver.receive();
           if ( message != null ) {
               /********* XML-specific code ***********/
                /********Step 1********/
             if ( message instanceof XMLMessage ) {
                XMLMessage xmlMessage = (XMLMessage) message;
                /********Step 2********/
                Document xmlDoc = xmlMessage.getXMLDOMDocument();
                System.out.println(
    "XMLMessage is: "+xmlMessage.getXMLInTextForm(xmlDoc.getDocumentElement()));
                /********Step 3********/
                printElementIds(xmlDoc);
             }
               /******** XML-specific code Ends ********/
             else if ( message instanceof TextMessage ) {
                 TextMessage textMessage = (TextMessage) message;
                 System.out.println("Text Message: "+textMessage.getText());
             }
             else if ( message instanceof BytesMessage ) {
                 //BytesMessage processing code
             }
             else if ( message instanceof MapMessage ) {
                 //MapMessage processing code
             }
           }
         }//try
         catch (Exception ex) {
           ex.printStackTrace ();
         }
       }//while(true )
     }//run()
     private void printElementIds(Document document){
       NodeList childs = document.getDocumentElement().getChildNodes();
       for (int i=0; i < childs.getLength(); i++) {
         if(childs.item(i).getNodeType() == Node.ELEMENT_NODE)
             System.out.println (
              i+" Element Id:"+((Element)childs.item(i)).getAttribute("Id"));
       }
     }    
     public static void main (String args[]) {
       if (args.length < 2 ) {
          System.out.println(
 "usage: Receiver <connectionFactoryName> <nameOfTheIncomingQueue>");
           return;
       }
       SampleXMLReceiver receiver = new SampleXMLReceiver(args[0], args[1]);
       Thread thread = new Thread (receiver);
       thread.start();
    }//main()
  }

清单 13 中可以看到,SampleXMLReceiver 类按照与 从 JMS 队列中接收消息 一节同样的步骤接收消息。惟一的区别在 清单 13 中标记为 “XML 专用代码” 的部分。SampleXMLReceiver 类的 run() 方法可能接收所有 JMS 支持的消息类型,包括 XML 消息。

接收消息的第 1 步,SampleXMLMessage 类的 run() 方法检查收到的消息的数据类型。如果收到的消息是 XML 消息,则将其转化成 XMLMessage 对象。现在可以使用 XMLMessage 类的 XML 处理方法处理收到的消息了。

run() 方法的第 2 步调用 getXMLInTextForm() 方法,该方法返回 XML 消息的文本表示。这就说明了如何使用 XMLMessage 类处理收到的消息。

也可使用 DOM API 处理收到的消息。第 2 步中得到 XML 消息的 DOM 表示。第 3 步在 printElementIds() 方法中包含简单的处理逻辑,该方法接收 DOM 文档并使用 DOM 打印 XML 消息中的 ID 列表。

除了上述向客户机发送和接收 XML 消息外,还可以尝试通过 XML-JMS 向普通(非 XML)JMS 客户机传递消息。这种客户机对 XML-JMS 也能正常工作,因为 XML-JMS 没有破坏或干扰已有的 JMS 功能。仅仅添加了 XML 编辑和处理支持。


回页首


用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理

集成这些技术来改进企业应用程序

第 5 页, 总 共 11 页

文档选项

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


对本教程的评价

帮助我们改进这些内容


签署 XML 消息

XML 消息中的签名支持

W3C 定义了两种重要的基于 XML 的安全标准:XML-Signature 和 XML-Encryption。

XML-Signature 规范定义了签署 XML 数据(完整的文档或者其中一部分)的 XML 格式,包装了 XML 格式中的数字签名。该规范定义了签署 XML 数据时使用的不同类型签名算法和加密密钥的模式。本教程中将 XML-Signature 规范称为 XML Digital Signatures,或者简写作 XMLDS

XML-Encryption 规范(或简称 XEnc)定义了加密 XML 数据保护 XML 数据的语法。

本教程不准备详细介绍这些规范,可以在 参考资料 中找到有关的详细信息。

这一节和下一节主要讨论如何按照 XMLDS 和 XEnc 执行下列任务:

  • 在 XML 消息中包装 X.509 证书。
  • 使用 X.509 证书保证 XML 消息(或者一部分)确实来自某个 JMS 用户。
  • 使用随机数作为 XML 消息的加密密钥。

为了说明这些任务,我将使用前面 ERP 应用程序场景的例子 中出现的消息交换场景。不过首先要简要地介绍一下 X.509 证书的工作原理。


回页首

X.509 证书

电子商务安全通常意味着用加密来保护 Internet 应用程序。加密算法有两种类型:对称加密和非对称加密。

对称密钥加密 使用一个密钥加密和解密数据。对称加密使用的密钥就像是通信方之间共享的秘密。因此对称密钥也称为秘密密钥。通信双方在通信前要交换密钥。

非对称密钥加密 使用一个密钥对(包括公钥和私钥)来保护通信数据。向他人公开公钥而自己保存好私钥。就是说使用非对称密钥加密时,需要一种分发公钥的机制。因此,非对称密钥加密常常与所谓的公钥基础设施(PKI)一起使用。

使用非对称加密向朋友发送加密消息的时候,要用朋友的公钥加密消息。您的朋友则使用私钥解密消息。另一方面,如果需要向朋友发送签名的消息,则使用您的私钥签署消息。您的朋友使用您的公钥验证签名。

对称加密和 PKI 各有自己的优缺点。对称加密通常比非对称加密的效率高。但是 PKI 不需要交换密钥。

后面 用 XML 格式包装密钥 一节将提供一个例子,将对称加密与 PKI 结合起来获得两种加密方法的好处。

X.509 证书是最常用的 PKI 标准。X.509 证书(或者简称证书)是包含下列数据的数据结构:

  1. 版本
  2. 序列号
  3. 签名算法
  4. 发布者
  5. 主体
  6. 公钥
  7. 扩展
  8. 签署值

下面逐个介绍 X.509 证书中的一些重要字段:

  • 版本:版本字段说明证书的版本。比如,Version 3 是撰写本教程时最新的版本。
  • 序列号:证书权威(CA)发出证书。所有证书用户都信任 CA。比如,生产企业有自己的 CA,所有部门都信任该 CA。该字段表示对该 CA 发出的每个证书都是惟一的序列号。
  • 签名算法:该字段指定 CA 用于签署证书数据的签名算法。比如,RSA-SHA1 是 IETF 定义的最常用的签名算法。
  • 发布者:该字段包含发出证书的 CA 名称。
  • 主体:主体字段包含证书所颁发给的实体的名称(或者证书的所有者)。比如,销售部门的证书包含主体名 “Marketing Department”。
  • 公钥:该字段包含公钥算法和代表证书主体的公钥(字节数组的形式)。可以根据 CA 使用的算法检查公钥的长度。
  • 扩展:扩展字段包含 X.509 Version 3 定义的一个或多个扩展字段。比如,X.509 Version 3 中定义的一个重要扩展是 “Subject Key Identifier”,该扩展可用于代替主体字段惟一的标识主体。
  • 签名值:该字段包含一个签名值,CA 用自身的私钥对全部证书数据计算得到该签名值。证书用户可使用 CA 的公钥验证 CA 的签名。如果验证成功,证书用户就可以确信该证书确实属于主体字段中所表明的那个人所有。

证书有很多使用模型,本教程将使用 ERP 应用程序场景的例子 中所述生产企业的例子介绍最简单的使用模型。

假设该加工企业的 CA 已经为所有企业部门颁发了证书,生产部门希望向财务部门发送经过签名的数据。生产部门使用与自身证书对应的私钥签署数据并发送到财务部门。财务部门得到生产部门的公钥并检查数据的签名。如果签名验证成功就表明这些数据确实来自生产部门。

这里讨论了使用证书的一个很简单的场景,随着本教程的讲述您将更多地了解证书的使用。

关于签署数据有一点需要指出:如果对完整的数据生成加密的签名,会需要大量 计算。使用数学校验和算法可以解决这个问题。这些算法消耗(摘要)需要签署的数据,生成具有特定长度的值。这个值称为摘要值,这种算法称为摘要算法。比如,SHA1 算法就是一种很常用的摘要算法。

计算得到需要签署的数据的摘要值后,对摘要值计算签名(而不是对全部数据计算签名)。这个过程与签署全部数据相比计算量要小一些。


回页首

XML 签名中包含的数据

基本的 XML 签名结构如 清单 14 所示。
清单 14. 使用 X.509 证书的典型 XML 签名


					
    <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo/>
        <SignatureValue/>
        <KeyInfo/>
    </Signature>
    

清单 14 仅仅展示了 XML 签名的框架,而不是要签署的 XML 数据所生成的签名。后面的 编辑引用元素 一节将在同一个消息中给出 XML 签名和签署后的 XML 数据,现在我们主要讨论 XML 签名所包含的成分。

Signature 元素包括了整个 XML 签名。在 清单 14 中可以看到,Signature 包含三个子元素:

  • SignedInfo 元素包括生成签名所用算法的细节、所签署数据的 URI,以及所签署数据的摘要值。
  • SignatureValue 包括组成数字签名的实际字节。
  • KeyInfo 包含生成签名必须使用的密钥的数据。

下面介绍这三个子元素,首先说明如何在 KeyInfo 元素中包含证书指针。


回页首

在 XML 签名中包含证书指针

清单 15 说明了如何使用 KeyInfo 元素在 XML 签名中包含证书指针。
清单 15. 展示 KeyInfo 元素的 XML 签名


					
    <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo/>
        <SignatureValue/>
        <KeyInfo>
            <X509Data>
                <!--A pointer to a certificate-->
            </X509Data>
        </KeyInfo>
    </Signature>
    

可以看到,这里使用 X509Data 元素在 XML 签名中包含证书指针。该元素是 W3C 在 XMLDS 中定义的签名模式的一部分。

您可能已经猜到,使用证书指针(比如证书的主体名)是为了让消息的接收方能够使用该指针取得证书。比如在企业场景这个例子中,可以假设企业中所有部门都维护有自己的证书数据库,其中存储有每个部门的证书。

可以有不同类型的证书指针(如主体名或者主体键标识符)。但本教程中仅使用主体名。

可以在公共证书仓库中保存您自己和其他人的证书(如您的朋友、伙伴、同事或老板)。您自己的证书和其他人的证书惟一的区别在于您的证书还包含私钥,其他证书只包含公钥,用于帮助验证其他人的签名。

清单 16 展示了 X509Data 元素的结构。
清单 16. XML 签名中 X509Data 元素的结构


					
    <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo/>
        <SignatureValue/>
        <KeyInfo>
            <X509Data>
                <X509SubjectName>
                    CN=Commercial Manager, 
                    O=Manufacturing Enterprise, 
                    OU=Commercial Department, 
                    C=PK
                </X509SubjectName>
            </X509Data>
        </KeyInfo>
    </Signature>
    

X509Data 包含一个 X509SubjectName 元素,指定证书的所有者。比如,清单 16X509SubjectName 元素的内容包括:


                    CN=Commercial Manager, 
                    O=Manufacturing Enterprise, 
                    OU=Commercial Department, 
                    C=PK

这种格式符合 RFC2253 的规定,这个 IOETF 国际标准定义了表示特殊名称的格式(请参阅 参考资料

X509SubjectName 元素值包含将下列名-值对连接起来的字符串:

  • CN 属性规定了证书所有者的姓名(清单 16 中为财务部门)。
  • O 属性指定了组织(该例中的加工企业)。
  • OU 属性指定了该组织中的单位或部门(这里是财务部门)。
  • C 属性指定了所在的国家(清单 16 中为巴基斯坦)。

接收方应用程序收到包含 X509SubjectName 元素的 XML 消息时,处理 X509SubjectName 元素,提取主体名,从密钥仓库中找到对应的证书并从证书中取出公钥。


回页首

编辑 SignedInfo 元素

SignedInfo 元素包含指定签署方用于生成 XML 签名的加密算法所需要的全部信息。SignedInfo 元素还包含对签署的数据及其摘要值的引用。

请阅读 清单 17 所示的 SignedInfo 元素。
清单 17. XML 签名中的 SignedInfo 元素


					
    <Signature Id=""
    xmlns="http://www.w3.org/2000/09/xmldsig#">
         <SignedInfo>
            <CanonicalizationMethod/>
            <SignatureMethod/> 
            <Reference/>
        </SignedInfo>
        <SignatureValue/>
        <KeyInfo>
            <X509Data>
                <X509SubjectName>
                    CN=Ishtiaq Hussain, 
                    O=ManufacturingEnterprise, 
                    OU=ProductionDepartment, 
                    C=PK
                </X509SubjectName>
            </X509Data>
        </KeyInfo>
    </Signature>
    

SignedInfo 元素包含三个子元素:CanonicalizationMethodSignatureMethodReference。现在逐个说明如何编辑这三个元素。


回页首

编辑 CanonicalizationMethod 元素

CanonicalizationMethod 元素指定了签署之前规范化 XML 数据所使用的算法。生成加密签名的过程对表示要签署的数据(这里是 XML 数据)的字节序列进行操作。就是说不同的字节序列将得到不同的签名。

清单 18 包含两个互相等价的 XML 标记,惟一的区别是属性的顺序:
清单 18. 等价的 XML 标记


					
    <item id="Q224.786" model="MQ1207"/>
    <item model="MQ1207" id="Q224.786"/>
    

虽然这两个 item 标记互相等价,但是具有不同的字节序列。就是说它们的加密签名也不同。如果签署了第一个 item 标记,但有人使用第二个 item 标记验证签名,即使在签名和验证中使用相同的密钥对,签名也会失败。

为了避免这个问题,已经作了一些尝试来定义 XML 数据的规范化算法。如果等价 XML 的不同表示应用规范化算法,就会得到同样的规范化形式。这意味着如果规范化是签名编辑和验证过程的一部分,签名验证就能正常工作。

最常用的 XML 规范化算法是 W3C 定义的。在 参考资料 中给出了与 W3C 规范化规范有关的文章。本教程中使用 W3C 规范化。

CanonicalizationMethod 元素的 Algorithm 属性指定了生成签名所使用的规范化算法。XMLDS 为签署 XML 所使用的不同算法定义了标识符。如果使用 W3C 规范化,则指定 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" 作为 Algorithm 属性的值。

Algorithm 属性的值参见 清单 19
清单 19. CanonicalizationMethod 元素指定了规范化算法


					
    <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
           <CanonicalizationMethod 
                Algorithm=
                "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            <SignatureMethod/> 
            <Reference/>
        </SignedInfo>
        <SignatureValue/>
        <KeyInfo>
            <X509Data>
                <X509SubjectName>
                    CN=Ishtiaq Hussain, 
                    O=ManufacturingEnterprise, 
                    OU=ProductionDepartment, 
                    C=PK
                </X509SubjectName>
            </X509Data>
        </KeyInfo>
    </Signature>
    



回页首

编辑 SignatureMethod 元素

顾名思义,SignatureMethod 元素指定了生成签名的加密算法。

本教程使用 RSA 签名算法与 SHA1 摘要算法。这两种算法的组合是 IETF 在 RFC2437 中定义的,常常被称为 RSA-SHA1。关于这些算法的细节请参阅 参考资料

XMLDS 定义的 RSA-SHA1 算法标识符是 "http://www.w3.org/2000/09/xmldsig#rsa-sha1"。

如果使用 RSA-SHA1 算法,XML 签名中的 SignatureMethod 元素就如 清单 20 所示。
清单 20. XML 签名中的 SignatureMethod 元素


					
  <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod 
         Algorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod 
         Algorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
      <Reference/>
    </SignedInfo>
    <SignatureValue/>
    <KeyInfo>
      <X509Data>
        <X509SubjectName>
              CN=Ishtiaq Hussain, 
              O=ManufacturingEnterprise, 
              OU=ProductionDepartment, 
              C=PK
        </X509SubjectName>
      </X509Data>
    </KeyInfo>
  </Signature>



回页首

编辑 Reference 元素

Reference 元素包含三类重要信息:

  • Reference 元素的 URI 属性
  • DigestMethod 元素的 Algorithm 属性
  • Reference 的子元素 DigestValue

Reference 元素的 URI 属性 包含所签署的 XML 数据的 URI。按照 XML-Signature 规范,可以将 XML 签名和所签署的数据放在同一个 XML 文档中,也可以将签名放在不包含所签署数据的 XML 文档中。

但在本教程中使用的 XML 签名都放在包含所签署数据的 XML 文档中。这样,如果 XML 签名和所签署的数据都位于同一 XML 文档中,就可以使用 XPath 指向签署的数据。

URI 属性值中使用 XPath 表达式。清单 21 说明销售部门如何使用 Reference 元素的 URI 属性值中的 XPath 指向希望签署的项目列表。这里不打算详细介绍 XPath,可以通过 参考资料 进一步了解这种技术。
清单 21. 指向所签署的 XML 数据的 Reference 元素的 URI 属性


					
  <Signature Id="cost-021-Signature"
      xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod 
          Algorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod 
          Algorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
      <Reference URI= "//*[@Id=cost-021]"> 
          <DigestMethod/>
          <DigestValue/> 
      </Reference>
    </SignedInfo>
    <SignatureValue/>
    <KeyInfo>
      <X509Data>
          <X509SubjectName>
              CN=Ishtiaq Hussain, 
              O=ManufacturingEnterprise, 
              OU=ProductionDepartment, 
              C=PK
          </X509SubjectName>
      </X509Data>
    </KeyInfo>
  </Signature>

DigestMethod 元素的 Algorithm 属性 指定了对签署数据计算摘要值所用的摘要算法。注意,该摘要算法用于要签署的规范化数据以生成摘要值。

本教程使用 SHA1 摘要算法。根据 XMLDS,SHA1 算法的标识符是 "http://www.w3.org/2000/09/xmldsig#sha1"。

注意,这里的 SHA1 算法与讨论 RSA-SHA1 签名算法(编辑 SignatureMethod 元素)时所述的摘要算法不是一回事。因为按照 RFC2437 的定义,SHA1 摘要算法是 RSA-SHA1 签名算法的一部分。本节后面将提到,摘要值是在要签署的数据的规范化形式上计算出来的。另一方面,签名值是在整个 SignedInfo 元素上计算出来的。

简言之,要使用 SHA1 摘要计算算法两次,一次是用于计算数据规范化形式的摘要值,一次是用于计算 SignedInfo 元素的签名值。

编辑 Algorithm 属性值以后,XML 签名如 清单 22 所示。
清单 22. 编辑 DigestMethod 元素的 Algorithm 属性后的 XML 签名


					
    <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
          <CanonicalizationMethod 
              Algorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
          <SignatureMethod 
              Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
          <Reference URI="//*[@Id=cost-021]">
              <DigestMethod Algorithm=
                  "http://www.w3.org/2000/09/xmldsig#sha1" />
              <DigestValue/> 
          </Reference>
        </SignedInfo>
        <SignatureValue/>
        <KeyInfo>
          <X509Data>
              <X509SubjectName>
                  CN=Ishtiaq Hussain, 
                  O=ManufacturingEnterprise, 
                  OU=ProductionDepartment, 
                  C=PK
              </X509SubjectName>
          </X509Data>
        </KeyInfo>
    </Signature>
    

ReferenceDigestValue 子元素 包含对所签署 XML 数据使用 DigestMethod 元素中指定的摘要算法计算得到的摘要值。

注意,摘要值采用原始二进制数据格式。如果直接将原始二进制数据包含在 XML 中可能引起 XML 解析器的误解。因此 XML-Signature 规范要求摘要值在保存到 DigestValue 元素之前一律使用 base64 编码。关于 base64 编码的更多信息,请参阅 参考资料

清单 23 展示了销售部门编辑 DigestValue 元素以后的 XML 签名。
清单 23. 包含摘要值的 DigestValue 元素


					
  <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod 
          Algorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod 
          Algorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
      <Reference URI="//*[@Id=cost-021]">
         <DigestMethod Algorithm=
              "http://www.w3.org/2000/09/xmldsig#sha1" />
          <DigestValue>up4NbeVu……</DigestValue> 
      </Reference>
    </SignedInfo>
    <SignatureValue/>
    <KeyInfo>
      <X509Data>
         <X509SubjectName>
             CN=Ishtiaq Hussain, 
             O=ManufacturingEnterprise, 
             OU=ProductionDepartment, 
             C=PK
          </X509SubjectName>
      </X509Data>
    </KeyInfo>
  </Signature>

现在已经完成了 SignedInfo 的三个子元素的编辑。并且已经在 在 XML 签名中包含证书指针 一节编辑了 KeyInfo 元素。因此还有一个元素(SignatureValue)就可以完成签名的编辑工作了。首先说明编辑 SignatureValue 元素必须经过的操作步骤。


回页首

编辑 SignatureValue 元素

Signature 的子元素 SignatureValue 包含销售部门需要编辑的加密签名的实际值(base64 编码形式)。销售部门可通过以下步骤计算签名值:

  1. 编辑前面 清单 14 中展示的 Signature 基本结构。
  2. 编辑 KeyInfo 元素,在 X509Data 元素中包含指向您的证书的指针,如 在 XML 签名中包含证书指针 一节所述。这时候的 XML 签名如 清单 16 所示。
  3. 编辑 SignedInfo 元素,指定要使用的签名算法。编辑 Reference 元素的 URI 属性。URI 属性值指向所签署的数据。得到的 XML 签名如 清单 22 所示。注意,DigestValue 元素是空的,还没有摘要值。
  4. 使用所选摘要算法(如 SHA1)对所签署的 XML 元素计算摘要值。
  5. 对摘要值用 Base64 编码。
  6. 将 base64 编码的摘要值包含在 DigestValue 元素中。现在的 XML 签名如 清单 23 所示。
  7. 现在 SignedInfo 元素就完成了。按照 RFC2437 使用 RSA-SHA1 算法计算 SignedInfo 元素的签名。用 Base64 编码签名值并包含到 SignatureValue 元素中。

这样 XML 签名就完成了,如 清单 24 所示。
清单 24. 完成后的 XML 签名


					
  <Signature Id="" xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm = 
            "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
      <Reference URI="//*[@Id=cost-021]">
          <DigestMethod Algorithm = "http://www.w3.org/2000/09/xmldsig#sha1" />
          <DigestValue>up4NbeVu……</DigestValue> 
      </Reference>
    </SignedInfo>
    <SignatureValue>M2s$xb53h4Ch0Elf=...</SignatureValue>
    <KeyInfo>
      <X509Data>
          <X509SubjectName>
              CN=Ishtiaq Hussain, 
              O=ManufacturingEnterprise, 
              OU=ProductionDepartment, 
              C=PK
          </X509SubjectName>
      </X509Data>
    </KeyInfo>
  </Signature>

下一节简单介绍如何在 XML-JMS 中引入签名支持。本系列的第 2 部分将使用 IBM XSS4J 实现这些功能。


回页首

在 XMLMessage 类中添加签名支持

实现 XMLMessage 一节中,开发了编辑和处理简单 XML 消息的 XMLMessage 类。这一节将扩展 XMLMessage 类,开发一个 SecureXMLMessage 类,该类包含编辑和处理安全 XML 消息的方法。

SecureXMLMessage 类包含通过程序编辑和处理前述 XMLDS 语法的方法。

SecureXMLMessage 类的方法是任何应用程序都很容易使用的高级方法。就是说应用程序开发人员可以使用 SecureXMLMessage 来编辑和处理 XML 消息,而不需要知道 XMLDS 和 XEnc 语法,甚至不需要知道如何使用 DOM 的细节。

后面的 XMLMessage 类增加加密支持 一节中,还将为 SecureXMLMessage 类增加加密支持。

清单 25 列出了 SecureXMLMessage 类的方法声明。本系列的下一部分将提供这些方法的实现。
清单 25. SecureXMLMessage 类中与签名有关的方法声明


					
    void loadCertificateStore ( 
                                String storeName,
                                String storePassword,
                                String keyName,
                                String keyPassword 
                              ){}
    public void sign ( String elementID,
                       String signatureID
                     ){}
    public void sign ( 
                      String elementID,
                      String signatureID,
                      String signatureAlgo,
                      String digestAlgo,
                      String canonicalizationAlgo
                       ){}
    public boolean verify (String signatureID) {}
    public String[] getAllSignatureIDs (){}
    public String[] getAllSignedXMLDataIDs (){}
    public String getX509SubjectName (String signatureID) {}
    public String getSignedXMLData (String signatureID) {}
    public X509Certificate getX509Certificate (String signatureID) {}
    public String getPublicKeyX509Certificate (String signatureID) {}
    

现在来看看这些方法是干什么的。

首先要注意,在使用 SecureXMLMessage 签名或验证签名之前,需要将证书存储加载到 SecureXMLMessage 对象中。证书存储包含证书和一个或多个私钥,用于签署或验证 XML 消息。

清单 25 中的 loadCertificateStore() 方法创建并加载证书存储对象。证书存储本身及其包含的私钥用口令保护。

loadCertificateStore() 方法有四个参数,分为两对。第一对参数包括证书存储的名称和口令。第二对参数包括私钥名及其口令。

清单 25 中的 sign() 方法编辑 signature 元素。签署 XML 数据需要与证书对应的私钥。要记住已经提供将私钥加载到 loadCertificateStore() 中所需要的信息。

清单 25 中提供了两个 sign() 方法,分别有五个参数和两个参数。带有五个参数的方法的前两个参数是 ID。需要签署的元素的 ID 作为第一个参数,要生成的 signature 元素的 ID 作为第二个参数。生成 signature 元素时,需要传递签名、摘要和规范化算法的标识符。sign() 方法的最后三个参数指定了这些加密算法的标识符。

带有两个参数的 sign() 方法与前面那个类似,只不过使用了签名、摘要和规范化算法的默认值。

经过签名的 XML 数据的接收方使用 verify() 方法验证 XML 数据的签名。清单 25 中的 verify() 方法是一个处理方法,接受 signature 元素的 ID 作为参数,如果签名有效则返回 true。

SecureXMLMessage 类中有一些方便的方法。比如,如果希望处理安全 XML 消息得到其中所有 signature 元素的 ID 列表,那么可以使用 清单 25 中的 getAllSignatureIDs() 方法。该方法处理安全 XML 消息并返回所有 signature 元素的 ID 数组。

可以使用 XMLMessage 类的 fetchElementWithID() 方法检索特定的签名。将 getAllSignatureIDs() 方法返回的签名 ID 列表中的一个传递给 fetchElementWithID() 方法。fetchElementWithID() 将返回指定的 signature 元素。

类似地,getAllSignedXMLDataIDs() 方法(也在 清单 25 中)返回将被签署以生成 XML 消息中不同签名的所有元素的 ID 列表。

如果希望访问将被签署以生成某个签名的 XML 数据,可使用 getSignedXMLData() 方法(在 清单 25 中)。该方法根据 signature 元素的 ID 返回将被签署以生成该签名的 XML 数据。

清单 25 中还包括 getX509Certificate() 方法,它根据签名元素的 ID 返回 Certificate 对象。该对象表示签署该 XML 数据时用来签署实体的证书。


回页首


用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理

集成这些技术来改进企业应用程序

第 6 页, 总 共 11 页

文档选项

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


对本教程的评价

帮助我们改进这些内容


保密 XML 消息

XML Encryption 规范

W3C XML-Encryption 规范(简称 XEnc)定义了加密 XML 数据来保密 XML 消息的语法。该规范允许您做很多事情,如:

  • 加密整个文档(XML 或其他文档,比如 zip 或图像文件),将加密的文档包含在 XML 文档中。
  • 加密 XML 元素或者元素的内容,将加密后的数据放在同一 XML 文档或者其他文档中。
  • 甚至可以加密以前加密的内容。

XML 加密可使用对称和非对称密钥。如 X.509 证书 一节所述,对称和非对称密钥各有优缺点。

也可以结合使用对称和非对称加密,充分利用两者的优点。为此,可以生成一个随机数,用这个随机数作为对称密钥加密数据,然后用对方的公钥(非对称)加密该随机数,将加密的数据和随机数放在 XML 消息中发送给对方。

收到消息时,对方首先使用私钥解密随机数,然后使用随机数解密发送的数据。后面的 在 XML 格式中包含加密密钥 一节将说明如何在 XML 加密中使用这种组合方式。

XML-Encryption 规范非常灵活,包含很多可能的方法。本教程不打算介绍这些方法,仅考虑在 XML 加密中使用随机数加密。

关于 XML 加密各方面的文章,请参阅 参考资料

为了说明 XML 加密的用法,后面将使用 ERP 应用程序场景 一节中所述的信息交换的例子。

使用 XML 加密一般需要做两件事:

  • 将加密后的数据放在 XML 消息中。
  • 将加密密钥放到 XML 格式中。

下面分别介绍这两个步骤。


回页首

将加密数据包含在 XML 文档中

假设财务部门希望加密 ERP 应用程序场景 一节 清单 2 中的 costs 元素。

XEnc 定义了名为 EncryptedData 的元素来存放加密数据。EncryptedData 元素代替了希望加密的明文元素。清单 26 中,清单 2 中的 costs 元素已经被替换成了 EncryptedData 元素。
清单 26. 代替要加密的明文元素的 EncryptedData 元素


					
    <?xml version="1.0" encoding="UTF-8"?>
    <quotation Id="0123" dated="Mon, 1 Aug 2005">
        <itemInquiryDate>Mon, 1 Aug 2005</itemInquiryDate> 
        <items>
            <item Id="item-021">
                <model>PF486</model> 
                <description>Power factor controller</description> 
                <quantity>95</quantity> 
                <unit>Nos.</unit>
            </item>
            <item Id="item-022">
                <model>KN34</model> 
                <description>Voltage controller</description> 
                <quantity>15</quantity> 
                <unit>Nos.</unit>
            </item>
            <!--Other items-->
        </items>
        <EncryptedData>
            <!--The cost data in encrypted form-->           
        </EncryptedData>
        <deliveryPeriods>
            <deliveryPeriods itemRef="#item-021" value="4" unit="weeks">
            <deliveryPeriods itemRef="#item-022" value="3" unit="weeks">
            <!--Delivery data for other items.-->           
        </deliveryPeriods>
        <Signature>
            <!--Details of the marketing department's signature.-->
        </Signature>
        <Signature>
            <!--Details of the production department's signature
               over the cost data.-->
        </Signature>
        <Signature>
            <!--Details of the production department's signature
               over the delivery period data.-->
        </Signature>
    </quotation>
    

现在说明如何编辑 EncryptedData 结构。EncrytedData 的基本结构如 清单 27 所示。
清单 27. EncryptedData 的基本结构


					
    <EncryptedData Type=" ">
        <EncryptionMethod Algorithm=""/>
        <KeyInfo/>
        <CipherData/>
    </EncryptedData>
    

可见 EncryptedData 元素包括属性 Type 和三个子元素:EncryptionMethodKeyInfoCipherData

Type 属性规定了加密的类型。XEnc 允许执行多种不同类型的加密。比如,可以加密 XML 元素或者某个 XML 元素的内容。本教程中只讨论加密 XML 元素。

加密 XML 元素时需要指定字符串 http://www.w3.org/2001/04/xmlenc#Element 作为 EncryptedData 元素的 Type 属性值。该字符串是 XEnc 为元素类型加密定义的标识符。

现在说明如何编辑 EncryptedData 的三个子元素。


回页首

编辑 EncryptionMethod 元素

如前所述,EncryptionMethod 元素指定了加密明文数据使用的加密算法。这里使用对称密钥来加密 XML 数据。本教程使用三重 DES,这可能是最常见的加密算法。

XEnc 定义了常用加密算法的标识符。三重 DES 的标识符是 http://www.w3.org/2001/04/xmlenc#3des-cbc。从 清单 28 可以看到,EncryptionMethod 元素的 Algorithm 属性值包含加密算法的标识符。
清单 28. 包含 EncryptionMethod 元素的 EncryptedData 结构


					
    <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#"  
        Type="http://www.w3.org/2001/04/xmlenc#Element">
        <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#3des-cbc"/>
        <KeyInfo/>
        <CipherData/>
    </EncryptedData>
    



回页首

编辑 KeyInfo 元素

接下来编辑 EncryptedDataKeyInfo 子元素。KeyInfo 元素在 XEnc 的模式中定义。它实际上与前面 在 XML 签名中包含证书指针 一节提到的 KeyInfo 元素相同。XEnc 借用了 XMLDS 的 KeyInfo 元素。不过这一次 KeyInfo 包含不同的数据。

注意这里使用随机数作为加密密钥。消息的作者使用随机数加密,接收者使用同一个数解密。

因此,EncryptedDataKeyInfo 子元素需要指定用于加密 XML 数据的随机数。XEnc 提供一个名为 EncryptedKey 的元素用于保存加密密钥。后面的 在 XML 格式中包含加密密钥 一节中将说明如何在 EncryptedKey 元素中保存经过加密的随机数。现在重点讨论如何使用 KeyInfo 元素指定对加密密钥的引用。

清单 29 中的 KeyInfo 元素有一个名为 RetrievalMethod 的子元素。
清单 29. KeyInfo 元素包含 RetrievalMethod 子元素


					
    <KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <RetrievalMethod URI="#encryptedKey2"
             Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/>
    </KeyInfo>
    

XEnc 借用了 XMLDS 的 RetrievalMethod 元素来指定获取加密密钥的方法。RetrievalMethod 元素有一个属性 Type,指定了用于获取密钥的方法。获取密钥有多种方法,其中之一是在 EncryptedKey 元素中寻找密钥。

如果密钥放在 EncryptedKey 元素中,XEnc 定义了 Type 属性值 "http://www.w3.org/2001/04/xmlenc#EncryptedKey"

清单 29 中的 RetrievalMethod 元素还包含属性 URI。该属性指定了到哪里寻找 EncryptedKey 元素。清单 29 中使用片段标识符来定位 EncryptedKey 元素。


回页首

编辑 CipherData 元素

还要把加密后的数据放在 EncryptedData 结构中。为此需要以下步骤:

  1. 生成明文数据的实际加密值。
  2. 对加密数据采用 Base64 编码。
  3. 编辑元素 CipherValue
  4. 将 base64 编码的加密数据放到 CipherValue 元素中。
  5. CipherValue 元素包含到 CipherData 中。
  6. CipherData 作为 EncryptedData 元素的直接子元素。

清单 30 展示了完成这些步骤之后的最终结果。
清单 30. 完整的 EncryptedData 结构


					
    <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#"  
        Type="http://www.w3.org/2001/04/xmlenc#Element">
        <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#3des-cbc"/>
        <KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <RetrievalMethod URI="#encryptedKey2"
                Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/>
        </KeyInfo>
        <CipherData>
            <CipherValue>Xe23fRG4$65837Jty7kik53546</CipherValue>
        </CipherData>
    </EncryptedData>
    



回页首

在 XML 格式中包含加密密钥

现在需要编辑 EncryptedKey 结构来包含加密后的密钥。EncryptedKey 结构和 清单 30 中的 EncryptedData 结构非常类似。EncryptedKey 结构如 清单 31 所示。
清单 31. EncryptedKey 结构


					
    <EncryptedKey Id="encryptedKey1" xmlns="http://www.w3.org/2001/04/xmlenc#">
        <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <X509Data>
                <X509SubjectName>
                    CN=Bilal Siddiqui, 
                    O=ManufacturingEnterprise,
                    OU=CommercialDepartment,
                    C=PK
                </X509SubjectName>
            </X509Data>
        </KeyInfo>
        <CipherData>
            <CipherValue>XnkCPKha+zT...</CipherValue>
        </CipherData>
        <ReferenceList>
            <DataReference URI="#cost-021"/>
            <!-- other data references-->
        </ReferenceList>
    </EncryptedKey>
    

清单 31 可以看到,EncryptedKey 结构包含与 EncryptedData 相同的子元素。这里就不需要再解释了。但是,清单 31 EncryptedKey 中还有一个子元素 ReferenceList

ReferenceList 元素包含一个 URI 列表,指向加密密钥使用的 EncryptedData 元素。每个 URI 都是 ReferenceList 的子元素 DataReference 的属性值。

另外,比较 EncryptedKey 的子元素 KeyInfo 和前面 XMLDS 清单 15 中的 KeyInfo 元素。这两个 KeyInfo 元素是相同的。知道为什么吗?

清单 2 中,生产部门使用自己的私钥签署 XML 消息。生产部门在 XML 签名中放入了证书指针(主体名),证书中包含匹配的公钥。接收方使用证书指针找到证书,使用证书中的公钥验证生产部门的签名。

而在 清单 31 中,生产部门使用接收方的公钥加密随机数。因此,生产部门在 XML 消息中包含接收方证书的指针。接收方利用对其证书的引用,使用相应的私钥解密随机数。

这就说明了为何清单 1531 中的 KeyInfo 元素具有相同的结构。两个 KeyInfo 元素都包含有证书指针。


回页首

安全的 XML 消息

现在已经讨论了 EncryptedDataEncryptedKey 元素的完整结构。应该给出包含 XML 加密以及 XML 签名的完整形式的报价单了,如 清单 32 所示。
清单 32. 包含 XML 加密部分与 XML 签名的 XML 消息


					
    <?xml version="1.0" encoding="UTF-8"?>
    <quotation Id="Q123" dated="Mon, 1 Aug 2005">
        <itemInquiryDate>Mon, 1 Aug 2005</itemInquiryDate> 
        <items>
            <item Id="item-021">
                <model>PF486</model> 
                <description>Power factor controller</description> 
                <quantity>95</quantity> 
                <unit>Nos.</unit>
            </item>
            <item Id="item-022">
                <model>KN34</model> 
                <description>Voltage controller</description> 
                <quantity>15</quantity> 
                <unit>Nos.</unit>
            </item>
            <!--Other items-->
        </items>
        <EncryptedData item="cost-021" xmlns="http://www.w3.org/2001/04/xmlenc#"  
            Type="http://www.w3.org/2001/04/xmlenc#Content">
            <EncryptionMethod
                Algorithm="http://www.w3.org/2001/04/xmlenc#3des-cbc"/>
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <RetrievalMethod URI="#encryptedKey1"
                        Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey">
	     </KeyInfo>
            <CipherData>
                <CipherValue>DeX5hfXedRGhs…...</CipherValue>
            </CipherData>
        </EncryptedData>
        <deliveryPeriods>
            <deliveryPeriods itemRef="#item-021" value="4" unit="weeks">
            <deliveryPeriods itemRef="#item-022" value="3" unit="weeks">
            <!--Delivery data for other items.-->           
        </deliveryPeriods>
        <EncryptedData Id="price-021" xmlns="http://www.w3.org/2001/04/xmlenc#"  
            Type="http://www.w3.org/2001/04/xmlenc#Content">
            <EncryptionMethod
                Algorithm="http://www.w3.org/2001/04/xmlenc#3des-cbc"/>
            <KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <RetrievalMethod URI="#encryptedKey2"
                    Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey">
            </KeyInfo>
            <CipherData>
                <CipherValue>Xe23fRG4$65837Jty7kik53546</CipherValue>
            </CipherData>
        </EncryptedData>
        <commercialTerms>
             <!--Details of commercial terms.-->
        </commercialTerms>
        <EncryptedKey Id="encryptedKey1" 
            xmlns="http://www.w3.org/2001/04/xmlenc#">
            <EncryptionMethod 
                Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
                    <X509SubjectName>
                        CN=Bilal Siddiqui, 
                        O=ManufacturingEnterprise, 
                        OU=CommercialDepartment, 
                        C=PK
                    </X509SubjectName>
                 </X509Data>
            </KeyInfo>
            <CipherData>
                <CipherValue>XnkCPKha+zTiGJaHVD=...</CipherValue>
            </CipherData>
            <ReferenceList>
                <DataReference URI="#cost-021"/>
            </ReferenceList>
        </EncryptedKey>
        <EncryptedKey Id="encryptedKey2" 
            xmlns="http://www.w3.org/2001/04/xmlenc#">
            <EncryptionMethod 
                Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
                <X509Data>
                    <X509SubjectName>
                        CN=Sajjad Ali, 
                        O=ManufacturingEnterprise, 
                        OU=MarketingDepartment, 
                        C=PK
                    </X509SubjectName>
                </X509Data>
            <CipherData>
                <CipherValue>HwXfw25xtf@dFKha+JaH...</CipherValue>
            </CipherData>
            <ReferenceList>
                <DataReference URI="#price-021"/>
            </ReferenceList>
        </EncryptedKey>
        <Signature Id="cost-021-Signature"
            xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod 
                    Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
                <SignatureMethod 
                    Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> 
                    <Reference URI="#cost-021">
                        <DigestMethod 
                            Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                        <DigestValue>j6lwx3rvEPO0vKtMup4Nbe...</DigestValue> 
                    </Reference>
            </SignedInfo>
            <SignatureValue>M2s$xb53h4Ch0ELf=</SignatureValue>
            <KeyInfo>
                <X509Data>
                    <X509SubjectName>
                        CN=Ishtiaq Hussain, 
                        O=ManufacturingEnterprise, 
                        OU=ProductionDepartment, 
                        C=PK
                    </X509SubjectName>
                    <X509Certificate>Gxwd4erytIdwjtPkwr...lGW</X509Certificate>
                </X509Data>
            </KeyInfo>
        </Signature>
        <Signature Id="price-021-Signature"
            xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod 
                    Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
                <SignatureMethod 
                    Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> 
                    <Reference URI="#price-021">
                        <DigestMethod 
                            Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                        <DigestValue>Yj46qtx3vEyp0vi693dho...</DigestValue> 
                    </Reference>
            </SignedInfo>
            <SignatureValue>KM32sfxh4i9h0fEfs=</SignatureValue>
            <KeyInfo>
                <X509Data>
                    <X509SubjectName>
                        CN=Bilal SIddiqui, 
                        O=ManufacturingEnterprise, 
                        OU=CommercialDepartment, 
                        C=PK
                    </X509SubjectName>
                    <X509Certificate>MXd4IsId5jPk0+gA...</X509Certificate>
                </X509Data>
            </KeyInfo>
        </Signature>
        <Signature Id="terms-021-Signature"
            xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod 
                    Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
                <SignatureMethod 
                    Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
                    <Reference URI="#terms-021">
                        <DigestMethod 
                            Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                        <DigestValue>Yj46qtx3vEyp0vi693dhoM...</DigestValue> 
                    </Reference>
            </SignedInfo>
            <SignatureValue>KM32sfxh4i9h0fEfs=</SignatureValue>
            <KeyInfo>
                <X509Data>
                    <X509SubjectName>
                         CN=Bilal SIddiqui, 
                         O=ManufacturingEnterprise, 
                         OU=CommercialDepartment, 
                         C=PK
                    </X509SubjectName>
                    <X509Certificate>MXd4IsId5jPk0+...</X509Certificate>
               </X509Data>
            </KeyInfo>
        </Signature>
    </quotation>
    



回页首

在 XMLMessage 类(XMLJMS)中增加加密支持

介绍完了 XEnc 语法,现在来看看如何为 XMLMessage 类增加签名支持class 一节所述的 SecureXMLMessage 增加 XEnc 支持。

加密之前需要将证书存储加载到 SecureXMLMessage 对象。为此可使用前面 XMLMessage 类增加签名支持 一节所述的 loadCertificateStore() 方法。

清单 33 展示了需要增加到 SecureXMLMessage 类中的主要加密方法。
清单 33. SecureXMLMessage 类中的加密方法


					
    encrypt(  String idOfPlaintext, 
              String idOfCyphertext, 
              String idOfEncKey, 
              String subjectNameOfRecipient) {}
    public string decrypt (String idOfEncryptedData) {}
    public string[] getAllEncryptedDataIDs() {}
    public string[] getAllEncryptedKeyIDs() {}
    

加载证书存储后,调用 清单 33 中所示的 encrypt() 方法。encrypt() 方法有下列参数:

  • 要加密的元素的 ID
  • 生成的加密数据的 ID
  • 生成的加密密钥的 ID
  • 用于加密密钥的接收方证书的主体名

解密的时候调用 清单 33 中所示的 decrypt() 方法。decrypt() 方法接收 EncryptedData 元素的 ID 并解密生成明文形式。假设通过在调用 decrypt() 方法之前调用 loadCertificateStore() 方法,解密需要的私钥已经被加载。

清单 33 还包括 getAllEncryptedDataIDs()getAllEncryptedKeyIDs() 方法,分别返回安全 XML 消息中所有 EncryptedDataEncryptedKey 元素的 ID 列表。通常在对每个 EncryptedData 元素调用 decrypt() 方法之前使用这些方法。

本系列的第 2 部分将说明如何使用 SecureXMLMessage 类。


回页首


用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理

集成这些技术来改进企业应用程序

第 7 页, 总 共 11 页

文档选项

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


对本教程的评价

帮助我们改进这些内容


结束语

结束语

本教程首先提出了加工企业中的一个消息交换场景。然后通过分析消息交换场景说明了在企业范围内使用 XML 消息的必要性。

然后介绍了 JMS,讨论了它的体系结构,说明 JMS provider 如何满足企业消息传递的需求。

然后讲述了客户机应用程序如何使用 JMS 功能。根据这些讨论提出了扩展 JMS 功能以引入 XML 消息支持的策略。然后根据该策略实现了一种支持 XML 感知的 JMS 引擎,称为 XML-JMS。

最后两节描述了两种基于 XML 的安全标准:XML-Signature 和 XML-Encryption。还讨论了如何在 JMS 应用程序中使用这些标准。

第 2 部分将探讨和使用来自 IBM alphaWorks 的 XML Security Suite for Java (XSS4J),它很好地包装了 XML-Signature 和 XML-Encryption 规范的全部功能。


回页首

        <!-Details of the production department's signature

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值