扩展 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 需要经过以下步骤:
- 实现自己的 XML 连接工厂,JMS 应用程序的 JNDI 客户机在收到 JMS 客户机的请求时可以实例化该连接工厂。XML 连接工厂创建的连接对象是 XML 感知的。连接工厂还管理自己到 JMS provider 的连接。
- 将 XML 链接工厂放到 XML-JMS 服务器上,以便 JMS 客户机能够查找您的 XML 连接工厂。
- 实现 XML 感知的连接和会话对象。
- 实现 XML 感知的队列接收器。队列接收器包含用于创建和处理 XML 消息、使用 JMS provider 处理非 XML 消息的代码。
- 实现自己的
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() 方法。因此不需要列出 清单 7 中 XMLQueueConnection 类的所有方法。可以在 下载 的 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 编辑和处理 集成这些技术来改进企业应用程序 | |
|
签署 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 证书(或者简称证书)是包含下列数据的数据结构:
- 版本
- 序列号
- 签名算法
- 发布者
- 主体
- 公钥
- 扩展
- 签署值
下面逐个介绍 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 元素,指定证书的所有者。比如,清单 16 中 X509SubjectName 元素的内容包括:
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 元素包含三个子元素:CanonicalizationMethod 、SignatureMethod 和 Reference 。现在逐个说明如何编辑这三个元素。
编辑 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>
| Reference 的 DigestValue 子元素 包含对所签署 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 编码形式)。销售部门可通过以下步骤计算签名值:
- 编辑前面 清单 14 中展示的
Signature 基本结构。 - 编辑
KeyInfo 元素,在 X509Data 元素中包含指向您的证书的指针,如 在 XML 签名中包含证书指针 一节所述。这时候的 XML 签名如 清单 16 所示。 - 编辑
SignedInfo 元素,指定要使用的签名算法。编辑 Reference 元素的 URI 属性。URI 属性值指向所签署的数据。得到的 XML 签名如 清单 22 所示。注意,DigestValue 元素是空的,还没有摘要值。 - 使用所选摘要算法(如 SHA1)对所签署的 XML 元素计算摘要值。
- 对摘要值用 Base64 编码。
- 将 base64 编码的摘要值包含在
DigestValue 元素中。现在的 XML 签名如 清单 23 所示。 - 现在
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 编辑和处理 集成这些技术来改进企业应用程序 | |
|
保密 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 和三个子元素:EncryptionMethod 、KeyInfo 和 CipherData 。 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 元素 接下来编辑 EncryptedData 的 KeyInfo 子元素。KeyInfo 元素在 XEnc 的模式中定义。它实际上与前面 在 XML 签名中包含证书指针 一节提到的 KeyInfo 元素相同。XEnc 借用了 XMLDS 的 KeyInfo 元素。不过这一次 KeyInfo 包含不同的数据。 注意这里使用随机数作为加密密钥。消息的作者使用随机数加密,接收者使用同一个数解密。 因此,EncryptedData 的 KeyInfo 子元素需要指定用于加密 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 结构中。为此需要以下步骤:
- 生成明文数据的实际加密值。
- 对加密数据采用 Base64 编码。
- 编辑元素
CipherValue 。 - 将 base64 编码的加密数据放到
CipherValue 元素中。 - 将
CipherValue 元素包含到 CipherData 中。 - 将
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 消息中包含接收方证书的指针。接收方利用对其证书的引用,使用相应的私钥解密随机数。 这就说明了为何清单 15 与 31 中的 KeyInfo 元素具有相同的结构。两个 KeyInfo 元素都包含有证书指针。
安全的 XML 消息 现在已经讨论了 EncryptedData 和 EncryptedKey 元素的完整结构。应该给出包含 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 消息中所有 EncryptedData 和 EncryptedKey 元素的 ID 列表。通常在对每个 EncryptedData 元素调用 decrypt() 方法之前使用这些方法。 本系列的第 2 部分将说明如何使用 SecureXMLMessage 类。
用 JMS 保护 XML 消息,第 1 部分: 扩展 JMS 以支持 XML 编辑和处理 集成这些技术来改进企业应用程序 | |
|
结束语 结束语 本教程首先提出了加工企业中的一个消息交换场景。然后通过分析消息交换场景说明了在企业范围内使用 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 规范的全部功能。
| | | |