在本教程中,您将了解有关 Web 服务安全性(Web Services Security,WS-Security)的信息。本教程针对这样的开发人员,他们希望在能够保证消息传递时不被篡改的环境中公开自己的服务,或在必须积极标识消息发送方的情况下公开自己的服务。术语“WS-Security”通常指处理加密和数字签名的一组规范,支持您创建安全应用程序。
为了按照本教程中的步骤进行操作,您应该具有 SOAP 的基本知识(可以通过阅读本系列教程的第 1 部分了解 SOAP)和 XML 的基本知识。SOAP 与编程语言无关,但本教程中的示例使用的是 Java ™ 和 Apache Axis2 项目。不过,其中的概念也适用于任何编程语言和环境。
本系列教程以假想的报社 Daily Moon 为例,为了提高在竞争激烈的环境中的工作效率,其员工将使用各种 Web 服务来创建工作流系统,我们将在此过程中讲述各个 Web 服务基本概念。
第 1 部分说明了 Web 服务背后的基本概念,并演示了如何使用 SOAP(后续教程讨论的大部分内容的基础规范)来将 Classifieds Department 连接到内容管理系统。
第 2 部分进一步深入说明如何使用 Web 服务描述语言(Web Services Description Language,WSDL)定义 Web 服务预期产生的消息,从而使团队更方便地创建服务以及连接到服务的客户机。
在第 3 部分中,团队希望准备一系列服务,并希望能方便地查找这些服务。与此对应,统一描述、发现和集成(Universal Description, Discovery and Integration,UDDI)提供了可用服务的可搜索注册中心,以便将自己的服务发布给其他人。
现在,在第 4 部分中,The Daily Moon 的发行人 Rudy 决定报社需要为访问其内部系统的 Web 服务制订更好的安全过程。
在有关 WS-Policy 的第 5 部分中,我们将讨论团队为了访问这些刚提供了安全保护的服务需要进行哪些更改。
第 6 部分重点讨论的将是互操作性,因为必须从单个系统访问来自几个不同实现的服务。第 6 部分还将讨论在 WS-I 证书中涉及的要求和测试。
最后,第 7 部分将演示如何使用业务流程执行语言(Business Process Execution Language,WS-BPEL)来从各个服务创建复杂应用程序。
接下来让我们更为详细了解一下本教程中将讨论的内容。
在本教程中,您将了解 The Daily Moon 报社团队如何使用 WS-Security 规范来保证本系列前面的教程中讨论的 Web 服务之一的安全。
在本教程中,您将了解以下内容:
- 什么是 WS-Security
- 对称和非对称加密间的差异
- 签名和加密间的差异
- 安全性对 SOAP 消息的影响
- 如何使用 Axis2 保护 SOAP Web 服务的安全
在开始进行相关工作前,您将需要获得一些工具。
本教程的大部分内容都是概念性的东西,但为了处理创建 SOAP 消息的代码,您将需要安装以下软件:
我们将演示 Apache Geronimo 的安装和用法,而此应用服务器也是 IBM 的 WebSphere Community Edition 的基础。还可以使用 WebSphere Application Server 等其他应用服务器。可以下载 Apache Geronimo。有关安装 Geronimo 的更多信息,请参见本系列的第 1 部分。
您将使用的是 Apache Axis2,其中包含了各种 SOAP 相关的 API,可极大地简化您的工作。可以从 Apache.org 下载 Apache Axis2。本教程使用的是 0.94 版,但应该也能使用更高版本。
Apache Axis2 Rampart 模块——Security for the Axis2 Web 服务引擎通过 Rampart 模块提供,而此模块并未包含在缺省安装中。请从 Apache Download Mirrors 下载此模块。
Apache WSS4J——尽管 Axis 本身将使用 Rampart,但在某些情况下,您将需要引用 WSS4J 类目录。请下载 WSS4J 程序包。
Java 2 Standard Edition 的 1.4.2 或更高版本——所有这些工具都是基于 Java 的,本教程中将要构建的服务和客户机也是如此。请下载 J2SE SDK。
TCPMon(可选)——如果能实际看到消息,则更容易理解 Web 服务应用程序内的运行情况。请下载 TCP Monitor,以查看进出 Web 服务的消息。
GnuPG(可选)——我们将进行的所有消息签名工作都由 Axis2 和 Java 本身进行,不过,如果希望逐个对文档进行签名处理(我们将对此进行简单的演示),则请下载 GnuPG。
您将还需要 Web 浏览器和文本编辑器。
概述
在讨论如何保护服务的安全前,具体了解组成服务的各个部分可帮助确定必须进行哪些工作。
本系列教程逐步说明了 Daily Moon 报社的员工的探索过程,发现了采用 Web 服务形式工作的新方法。首先从 Classifieds Department 开始,该部门决定允许其他人通过使用 SOAP 消息(可以通过 HTTP 发送的 XML 消息)访问其系统。
例如,“for sale”子类别中的分类广告数量的请求可能与清单 1 所示类似。
清单 1. 示例 SOAP 消息
<?xml version='1.0' ?><env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> <env:Header> </env:Header> <env:Body> <cms:getNumberOfArticles xmlns:cms="http://www.daily-moon.com/cms"> <cms:category>classifieds</cms:category> <cms:subcategory>forsale</cms:subcategory> </cms:getNumberOfArticles> </env:Body></env:Envelope> |
整个消息称为信封,其内容由 Header 和主体组成。Header 包括有关消息本身的信息(如路由信息)或要由位于发送方和最终接收方之间可能对消息进行处理的“SOAP 中间层”或服务处理的信息。(在这种情况下,可以参见任何 Header,但这正是即将放置我们的安全信息的位置。)消息的主体包括“有效负载”,其中含有要传递给 Web 服务的实际数据。
在本例中,有效负载为 getNumberOfArticles
元素及其内容。
该报社的 IT 部门的 Gene 和 Frances 建立了一个处理 Web 服务请求的系统和用于发现和自动为服务创建客户机的基础设施。现在,该报社的发行人 Rudy 坚持要他们找到一种方法来防止对这些系统的非授权访问。
基础 SOAP 规范并不是针对保护消息安全而提供的,而将这个任务留给了扩展规范。困难之处源自 Web 服务应用程序本身的特性。在大多数情况下,我们都使用 SOAP over HTTP,这意味着每个消息都必须经过一个或多个中间节点,其中的任何节点都可以读取和/或更改消息。而这假定的是 SOAP 请求本身是直接的请求。在某些情况下,SOAP 消息专门设计为要通过多个节点才能达到其最终的目的地。
最终的结果是,我们需要防止确定的接收方之外的其他人读取敏感信息,从而防止窃听。我们还需要防止确定的发送方之外的其他人发送消息,以防止未授权的访问。
SOAP 规范提供了添加安全性信息的方法——我们可将信息添加到信封的 Header 元素中——但并没有指定该信息应该是什么样的。为了处理此问题,我们需要使用 WS-Security。
在保证 SOAP 消息交换的安全方面存在三个主要问题,而 WS-Security 提供了解决所有这些问题的答案,但并不是直接方法。事实上,规范并不考虑如何保护消息,而是讨论如何让接收方知道您在如何保护消息。为了进行实际的保护工作,WS-Security 引用了其他的规范。接下来让我们看看如何进行此工作。
第一个问题是对客户机进行标识和身份验证。由于存在很多种创建安全令牌的不同方法,因此 WS-Security 并未指定任何特定的方法,而是定义了应如何在 SOAP 消息中传输不同的安全标记。也就是说,它让接收方知道如何从消息中提取安全令牌来进行处理。
第二个问题是确保消息的完整性。WS-Security 使用数字签名处理此任务,它并没有自己提出一个全新的东西,而是采用了 XML Signature 规范。XML Signature 是一项 W3C 建议规范,提供了对 XML 文档进行数字签名的机制。
第三个问题是保护消息在传递过程中不被窃听。同样,WS-Security 采用了另一个 W3C 标准,即 XML Encryption;此标准提供了对 XML 文档进行加密的机制。
在本教程中,通过 Gene 和 Frances 进行相应的工作来保护现有 Classifieds 服务的过程,我们将了解如何使用这些标准来影响所使用的实际 SOAP 消息。
在我们讨论如何进行更改前,最好了解一下 Gene 和 Frances 面临的现状。Classifieds 的服务是使用 Axis2 实现的,而这意味着它包含在 *.aar 文件中。CMSService.aar 文件包含三个文件,如清单 2 中所示。
清单 2. 原始服务的内容
CMSService.classmeta-inf/Manifest.MFmeta-inf/services.xml |
此类本身相当简单,接受一个列出类别的 SOAP 消息,并返回包含该类别的广告数量的 SOAP 消息。它将按照 services.xml 文件(请参见清单 3)所定义的方式执行此功能。
清单 3. 原始 services.xml 文件
<service name="CMSService"> <description> This is a sample Web Service for the newspaper's Content Managment System. </description> <parameter name="ServiceClass" locked="false">CMSService</parameter> <operation name="getNumberOfArticles"> <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/> </operation></service> |
保护服务的安全仅涉及到对 services.xml 文件进行处理;Gene 和 Frances 完全不必接触实际的 Java 类。
客户机本身相当简单,仅发出并显示请求,如清单 4 中所示:
清单 4. 原始客户机类
import org.apache.axis2.Constants;import org.apache.axis2.addressing.EndpointReference;import org.apache.axis2.client.Options;import org.apache.axis2.client.ServiceClient;import org.apache.axiom.om.OMElement;import org.apache.axiom.om.OMAbstractFactory;import org.apache.axiom.soap.SOAPFactory;import org.apache.axiom.om.OMNamespace;import org.apache.axis2.context.ConfigurationContext;import org.apache.axis2.context.ConfigurationContextFactory;public class ClassifiedClient { private static EndpointReference targetEPR = new EndpointReference( "http://localhost:8888/axis2/services/CMSService"); public static OMElement getNumOfArticlesOMElement() { SOAPFactory fac = OMAbstractFactory.getSOAP12Factory(); OMNamespace omNs = fac.createOMNamespace( "http://daily-moon.com/cms", "cms"); OMElement method = fac.createOMElement("getNumberOfArticles", omNs); OMElement value = fac.createOMElement("category", omNs); value.addChild(fac.createOMText(value, "classifieds")); method.addChild(value); return method; } public static void main(String[] args) { try { OMElement payload = ClassifiedClient.getNumOfArticlesOMElement(); Options options = new Options(); options.setTo(targetEPR); options.setTransportInProtocol(Constants.TRANSPORT_HTTP); ServiceClient sender = new ServiceClient(); sender.setOptions(options); OMElement result = sender.sendReceive(payload); String response = result.getText(); System.out.println("There are "+response+ " classifieds at the moment."); } catch (Exception e) { System.out.println(e.toString()); } }} |
请注意,Gene 已将端点端口更改为 8888,而不是 Geronimo 所侦听的 8080 端口。他之所以这样做,是为了插入其他步骤,以便能看到往返传递的消息。
为了查看实际的 SOAP 消息,请从 http://ws.apache.org/commons/tcpmon/download.cgi 下载 TCPMon。(Axis2 提供了 SOAPMonitor,但 Gene 发现除了设置方面的麻烦以外,它仅显示 SOAP,却没有显示必要的原始消息,而后者正是我们希望查看的内容。)
下载后,请解压缩应用程序,并运行 tcpmon-1.0-bin\build\tcpmon.bat
文件。单击 Admin 选项卡,并创建新侦听器,如图 1 中所示。
图 1. TCPMon 的 Admin 选项卡
选择 8888 作为要侦听的端口,并指定 8080 为目标端口,以便使 TCPMon 位于客户机和服务器之间。单击 Add,然后单击 Port 8888 选项卡。单击 XML Format 复选框。现在,当 Frances 运行客户机时,Gene 可以看到请求和响应,如图 2 中所示。
图 2. 原始请求和响应
在继续之前,让我们简单看一下实际的消息,以便了解进行了哪些更改。
消息本身非常简单。请求仅请求系统中归类广告的数量,如清单 5 中所示:
清单 5. 原始请求
<?xml version='1.0' encoding='UTF-8'?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header /> <soapenv:Body> <cms:getNumberOfArticles xmlns:cms="http://daily-moon.com/cms"> <cms:category>classifieds</cms:category> </cms:getNumberOfArticles> </soapenv:Body> </soapenv:Envelope> |
响应也同样简单,如清单 6 中所示:
清单 6. 原始响应
<?xml version='1.0' encoding='UTF-8'?> <soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header /> <soapenv:Body> <resp:numberOfArcticles xmlns:resp="http://daily-moon.com/cms/" xmlns:tns="http://ws.apache.org/axis2" >42</resp:numberOfArcticles> </soapenv:Body> </soapenv:Envelope> |
在这两个消息中,Frances 将在 Header 元素中放入其他信息,供服务器(或客户机)进行处理。
目前任何人都可以向服务发送请求并获取响应,而这正是让 Rudy 感到紧张的原因。他希望对服务进行设置,以使得只有授权用户才能获得信息,而竞争者无法截取信息。Rudy 指出这是一个不错的概念验证系统。他真正担心的是更为复杂的系统,如向财务系统添加内容或进行访问的系统。但概念是相同的。
为了提供这个安全机制,Gene 和 Frances 需要使用加密及其相关技术——签名。
加密和签名
大部分安全性措施都涉及到某种形式的加密或数字签名中的加密,因此在 Frances 让 Gene 开始进行服务的安全配置前,她希望确保他们能理解自己即将进行的工作。
加密是对信息进行遮蔽以使其在没有特殊知识的情况下不能阅读的过程。已经出现了很多不同类型的加密方式。现代加密是应用于消息的定义良好的转换序列和某种形式的“密钥”。所得到的结果是无法阅读的杂乱数据。解密是与此相反的过程,也涉及到密钥,因此,通过将对密钥进行保密,可以很好地确保其他人都不能阅读经过加密的消息。现代加密方法可以根据是使用一个密钥还是两个密钥来进行分类。
在对称密钥算法(例如,DES)中,发送方和接收方必须事先确定共享密钥,并将其对其他各方保密;发送方使用此密钥进行加密,而接收方使用相同的密钥进行解密。在这种情况下,除了密钥应该由且仅由涉及的双方知道外,密钥本身并不重要。
在非对称密钥算法(如 RSA)中,存在两个分开的密钥:公钥向外公开,都知道其属于某个特定的个人(或组织),而会将对应的私钥保密。采用公钥加密的消息仅能由私钥进行解密,因此任何人都可以向特定个人发送保密消息。但反过来也成立,因此可使用某人的公钥解密的消息一定是由此人发出的。我们将说明这如何在对 SOAP 消息进行加密和签名的过程中应用。
.
Gene 决定首先看看实际消息文本如何受加密影响。她首先从文本文件 msg.txt 着手,此文件仅包含一行内容,如清单 7 中所示。
清单 7. 目标文本
Hello, world! |
为了简单起见,他使用了免费的 GnuPG 程序来执行清单 8 中所示的命令。
清单 8. 加密命令
gpg.exe -c -a --cipher-algo 3DES msg.txt |
此命令将使该程序使用对称密钥算法 3DES 来对文件进行加密。程序会询问 passphrase(Gene 输入的是“password”),然后将生成文件 msg.txt.asc。可以在清单 9 中看到此文件的内容。
清单 9. 经过加密的文件
-----BEGIN PGP MESSAGE-----Version: GnuPG v1.4.2.2 (MingW32)jA0EAgMCqFjZXeujyOJgySoKQ2qhpCpGERKpFn0iKms4kwjpI51BLcoTyH4p61YJkDAiRMbC6PfCBmg==G4EN-----END PGP MESSAGE----- |
当然,没有人能够据此猜出原始文本。成功后,Gene 继续处理数字签名的问题。
数字签名使用加密技术,但签名的目的不同。签名不会对消息进行遮蔽,而是让接收方相信两个信息的真实性:消息的发送方和消息本身。
它的工作方式是这样的。发送方使用其私钥对消息进行加密。此“签名”将随后与原始消息一起发送到接收方。接收方尝试验证签名,即使用发送方的公钥对签名进行解密,并将结果与原始消息进行比较。如果此过程成功完成,接收方可以确保发送方为消息的唯一发出者,因为除了发送方之外,没有人具有其私钥。而且,如果原始消息在传输过程中发生了更改,解密后的签名将不会与其匹配,因此成功的签名验证也意味着没有对消息进行篡改。
通常发送方不会对整个消息进行签名,而计算消息的一个“摘要”(使用加密散列函数),对此摘要进行签名,并将其随未加密的消息一起传递。接收方计算所接收到的消息的摘要,并将其与解密得到的签名摘要进行比较。这可提供相同的功能,但不会让每个消息的大小因此而翻倍。(它还减少了创建和验证签名的处理时间。)
成功后 Gene 大受鼓舞,决定对此进行实际应用。他要对之前进行了加密的 msg.txt 文件进行数字签名。由于通过 GnuPG 可方便地对文件进行签名,因此他将使用同一个软件,不过稍后将使用不同的技术来创建实际应用程序所使用的密钥。
第一步是使用清单 10 中的命令创建密钥对。
清单 10. 生成密钥对
gpg.exe --gen-key |
程序将询问有关密钥的很多问题,如 Gene 的姓名、电子邮件等等。他采用了缺省密钥参数(DSA 算法和 2048 位密钥长度),并提供了姓名“Jon Dow”。此操作会将密钥存储在 Gene 的 home 文件夹的密钥存储库文件中。
现在他可以对消息进行签名了。他使用了清单 11 中所示的命令。
清单 11. 对文档进行签名
gpg.exe --clearsign -u "Jon Dow" msg.txt |
此时,程序将询问与“Jon Dow”的密钥相关的密码,然后生成 msg.txt.asc 文件,如清单 12 中所示。
清单 12. 经过签名的文档
-----BEGIN PGP SIGNED MESSAGE-----Hash: SHA1Hello, world!-----BEGIN PGP SIGNATURE-----Version: GnuPG v1.4.2.2 (MingW32)iD8DBQFEhftF06oTl3UESDQRArHsAJ0bE2qUEeVb5IDz4gQuRCgOes6v7gCfQhbJl356yO+YTkJUJZx4KoTTzok==ld3Y-----END PGP SIGNATURE----- |
接收到此消息的人将知道使用“Jon Dow”的公钥对此签名进行解密,对未加密的消息进行散列操作(使用 SHA1),然后对二者进行比较。
现在 Gene 和 Frances 已经了解了加密和签名如何工作,就可以开始对实际服务进行保护了。
保护服务的安全
保护服务的安全涉及到一系列必须在发送首个安全消息前进行的步骤;虽然这些步骤都不特定于实际的 WS-Security 规范(不管团队将用于生成消息的软件如何),但它们非常重要,会以某种形式在任何 WS-Security 安装中进行。
现在 Gene 已经了解了签名和加密的应用,接下来需要开始准备团队自己的安装了。他需要首先创建密钥存储库,其中将包括可能需要的所有私钥-公钥对。为此,他将使用随 JDK 一起提供的 keytool 应用程序。
为了创建新密钥和生成密钥存储库,他将执行以下命令,如清单 13 中所示。
清单 13. 创建密钥对和密钥存储库
>cd %JAVA_HOME%\bin>keytool -genkey -keystore mykeys.jks -alias geneEnter keystore password: mykeystorepasswordWhat is your first and last name? [Unknown]: Gene TellurideWhat is the name of your organizational unit? [Unknown]: Information TechnologiesWhat is the name of your organization? [Unknown]: The Daily MoonWhat is the name of your City or Locality? [Unknown]: New YorkWhat is the name of your State or Province? [Unknown]: NYWhat is the two-letter country code for this unit? [Unknown]: USIs CN=Gene Telluride, OU=Information Technologies, O=The Daily Moon, L=New York, ST=NY, C=US correct? [no]: yesEnter key password for <gene> (RETURN if same as keystore password): mypassword |
命令行告知工具生成密钥对,并将其存储在 mykeys.jks 文件中。密钥对的别名为 gene,从而让我们能方便地对其进行引用。Frances 可以创建自己的密钥,并将其存储在相同的 mykeys.jks 文件中。
现在他们已经准备好开始进行保护服务本身安全的相关工作。
将 WS-Security 添加到服务的第一步是在服务器上启用它。Apache Axis2(团队在其上运行其服务的 Web 服务引擎)是采用模块化方式构建的,相应的 WS-Security 模块称为 Rampart(有关下载信息,请参见先决条件)。下载了 Rampart 后,将 rampart-1.0.mar 文件放置在您的 Axis 安装的 modules 目录中。例如,Gene 的安装的 modules 文件夹位于 C:\SW\geronimo-1.0\config-store\32\war\WEB-INF\modules。
接下来,Gene 需要使得此模块全局可用,因为在 Axis2 后台工作的“处理程序链”中,需要使其参与系统,然后才能将消息定向到特定服务。重新启动 Geronimo,并从以下位置登录到 Axis2 管理页:http://localhost:8080/axis2/axis2-admin/login。(用户名为 admin,密码为 axis2。)单击 Engage Module/For all services,并选择 rampart-1.0。单击 Engage,如图 3 中所示。
图 3. 使 Rampart 模块参与系统
您还可以通过将 Rampart 模块添加到 axis2.xml 文件中使其参与系统,正如我们在了解如何保护客户机安全时将看到的一样。
现在已经配备了基础设施,Frances 就可以开始对实际服务进行安全保护工作了。首先,她关闭了 Geronimo,因为她希望更新位于 CMSService.aar 存档中的 services.xml 文件。(还可以选择更新并重新加载服务,但她选择了直接更改存档。)本教程的安装中,*.aar 文件位于以下位置:C:\SW\geronimo-1.0\config-store\32\war\WEB-INF\services\CMSService.aar,她对 services.xml 文件更新,以要求使用时间戳,如清单 14 中所示。
清单 14. 向服务添加时间戳
<service name="CMSService"> <description> This is a sample Web Service for the newspaper's Content Managment System. </description> <parameter name="ServiceClass" locked="false">CMSService</parameter> <parameter name="InflowSecurity"> <action> <items>Timestamp</items> </action> </parameter> <parameter name="OutflowSecurity"> <action> <items>Timestamp</items> </action> </parameter> <operation name="getNumberOfArticles"> <messageReceiver class= "org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/> </operation></service> |
Axis2 允许具体控制出入服务的消息流。在本例中,她告知 Web 服务引擎要求传入消息包括时间戳,并会在返回给客户机的传出消息中包括时间戳。
稍后我们将了解此更改对服务器和客户机间传递的消息的影响,但当 Frances 尝试运行测试时,TCPMon 显示服务器返回了错误,而不是预期的信息,如清单 15 中所示。
清单 15. 缺少 Security Header
<?xml version='1.0' encoding='UTF-8'?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing"> <soapenv:Header> <wsa:ReplyTo> <wsa:Address> http://www.w3.org/2005/08/addressing/anonymous </wsa:Address> </wsa:ReplyTo> <wsa:MessageID> urn:uuid:1CA9E94A9C7FE9D5B311507328796251 </wsa:MessageID> <wsa:Action> http://www.w3.org/2005/08/addressing/fault </wsa:Action> </soapenv:Header> <soapenv:Body> <soapenv:Fault> <faultcode>soapenv:Client</faultcode> <faultstring>WSDoAllReceiver: Request does not contain required Security header</faultstring> <detail> <Exception>org.apache.axis2.AxisFault: WSDoAllReceiver: Request does not contain required Security header at ... </Exception> </detail> </soapenv:Fault> </soapenv:Body> </soapenv:Envelope> |
此处重要的是,Frances 现在知道它已经在工作了;除非符合为服务配置的安全性要求,否则引擎就不会接受消息。
现在她可以着手处理客户机了。
从很多方面而言,客户机也是一种服务器,因为它也发送和接收 SOAP 消息,因此 Frances 保护客户机的安全的第一步自然是将 rampart-1.0.mar 添加到客户机安装。为此,她首先创建了一个新目录 <CLIENT_HOME>\axis-repo\modules,其中 <CLIENT_HOME> 为 ClassifiedClient.class 文件所在的目录。她随后将 rampart-1.0.mar 文件添加到该目录。她还创建了第二个目录 <CLIENT_HOME>\axis-repo\conf,并在其中创建了一个新文件 axis2.xml。此文件包含清单 16 中所示的代码。
清单 16. 原始 axis2.xml 文件
<axisconfig name="AxisJava2.0"> <!-- Engage the security module --> <module ref="ramart"/> <parameter name="OutflowSecurity"> <action> <items>Timestamp</items> </action> </parameter><!-- <parameter name="InflowSecurity"> <action> <items>Timestamp</items> </action> </parameter> --> <!-- ================================================= --> <!-- Parameters --> <!-- ================================================= --> <parameter name="hotdeployment" locked="false">true</parameter> <parameter name="hotupdate" locked="false">false</parameter> <parameter name="enableMTOM" locked="false">true</parameter> <!-- Uncomment this to enable REST support --> <!-- <parameter name="enableREST" locked="false">true</parameter>--> <parameter name="userName" locked="false">admin</parameter> <parameter name="password" locked="false">axis2</parameter> <!-- ================================================= --> <!-- Message Receivers --> <!-- ================================================= --> <!--This is the Deafult Message Receiver for the system , if you want to have MessageReceivers for --> <!--all the other MEP implement it and add the correct entry to here, so that you can refer from--> <!--any operation --> <!--Note : You can ovride this for particular service by adding the same element with your requirement--> <messageReceivers> <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only" class="org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver"/> <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out" class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/> </messageReceivers> <!-- ================================================= --> <!-- Transport Ins --> <!-- ================================================= --> <transportReceiver name="http" class="org.apache.axis2.transport.http.SimpleHTTPServer"> <parameter name="port" locked="false">6060</parameter> <!--If you want to give your own host address for EPR generation--> <!--uncommet following paramter , and set as you required.--> <!--<parameter name="hostname" locked="false">http://myApp.com/ws</parameter>--> </transportReceiver> <transportReceiver name="tcp" class="org.apache.axis2.transport.tcp.TCPServer"> <parameter name="port" locked="false">6061</parameter> <!--If you want to give your own host address for EPR generation--> <!--uncommet following paramter , and set as you required.--> <!--<parameter name="hostname" locked="false">tcp://myApp.com/ws</parameter>--> </transportReceiver> <!-- ================================================= --> <!-- Transport Outs --> <!-- ================================================= --> <transportSender name="tcp" class="org.apache.axis2.transport.tcp.TCPTransportSender"/> <transportSender name="local" class="org.apache.axis2.transport.local.LocalTransportSender"/> <transportSender name="jms" class="org.apache.axis2.transport.jms.JMSSender"/> <transportSender name="http" class="org.apache.axis2.transport.http.CommonsHTTPTransportSender"> <parameter name="PROTOCOL" locked="false">HTTP/1.1</parameter> <parameter name="Transfer-Encoding" locked="false">chunked</parameter> </transportSender> <transportSender name="https" class="org.apache.axis2.transport.http.CommonsHTTPTransportSender"> <parameter name="PROTOCOL" locked="false">HTTP/1.1</parameter> <parameter name="Transfer-Encoding" locked="false">chunked</parameter> </transportSender> <!-- ================================================= --> <!-- Phases --> <!-- ================================================= --> <phaseOrder type="inflow"> <!-- System pre defined phases --> <phase name="Transport"> <handler name="RequestURIBasedDispatcher" class="org.apache.axis2.engine.RequestURIBasedDispatcher"> <order phase="Dispatch"/> </handler> <handler name="SOAPActionBasedDispatcher" class="org.apache.axis2.engine.SOAPActionBasedDispatcher"> <order phase="Dispatch"/> </handler> </phase> <phase name="Security"/> <phase name="PreDispatch"/> <phase name="Dispatch" class="org.apache.axis2.engine.DispatchPhase"> <handler name="AddressingBasedDispatcher" class="org.apache.axis2.engine.AddressingBasedDispatcher"> <order phase="Dispatch"/> </handler> <handler name="SOAPMessageBodyBasedDispatcher" class="org.apache.axis2.engine.SOAPMessageBodyBasedDispatcher"> <order phase="Dispatch"/> </handler> <handler name="InstanceDispatcher" class="org.apache.axis2.engine.InstanceDispatcher"> <order phase="PostDispatch"/> </handler> </phase> <!-- System pre defined phases --> <!-- After Postdispatch phase module author or or service author can add any phase he want --> <phase name="OperationInPhase"/> </phaseOrder> <phaseOrder type="outflow"> <!-- user can add his own phases to this area --> <phase name="OperationOutPhase"/> <!--system predefined phase--> <!--these phase will run irrespective of the service--> <phase name="PolicyDetermination"/> <phase name="MessageOut"/> </phaseOrder> <phaseOrder type="INfaultflow"> <phase name="PreDispatch"/> <phase name="Dispatch" class="org.apache.axis2.engine.DispatchPhase"> <handler name="RequestURIBasedDispatcher" class="org.apache.axis2.engine.RequestURIBasedDispatcher"> <order phase="Dispatch"/> </handler> <handler name="SOAPActionBasedDispatcher" class="org.apache.axis2.engine.SOAPActionBasedDispatcher"> <order phase="Dispatch"/> </handler> <handler name="AddressingBasedDispatcher" class="org.apache.axis2.engine.AddressingBasedDispatcher"> <order phase="Dispatch"/> </handler> <handler name="SOAPMessageBodyBasedDispatcher" class="org.apache.axis2.engine.SOAPMessageBodyBasedDispatcher"> <order phase="Dispatch"/> </handler> <handler name="InstanceDispatcher" class="org.apache.axis2.engine.InstanceDispatcher"> <order phase="PostDispatch"/> </handler> </phase> <!-- user can add his own phases to this area --> <phase name="OperationInFaultPhase"/> </phaseOrder> <phaseOrder type="Outfaultflow"> <!-- user can add his own phases to this area --> <phase name="OperationOutFaultPhase"/> <phase name="PolicyDetermination"/> <phase name="MessageOut"/> </phaseOrder> </axisconfig> |
此代码中的大部分都是样本,摘自其他 Axis2 示例。不过,在顶部的粗体部分,Frances 添加了特定的代码来告知客户机发送给服务器的消息添加时间戳。她选择不处理服务器返回的时间戳(至少目前如此)。不过,与服务不同,客户机不会自动遵循这些指令。她必须对实际类进行更改。
为了客户机类进行 axis2.xml 文件中指定的更改,它必须能够识别这些内容。为此,Frances 将相应的配置添加到了 ServiceClient
的创建过程中,如清单 17 中所示。
清单 17. 从 ClassifiedClient.java 调用新配置
... public static void main(String[] args) { try { OMElement payload = ClassifiedClient.getNumOfArticlesOMElement(); ConfigurationContext configContext = ConfigurationContextFactory .createConfigurationContextFromFileSystem( "axis-repo", null); Options options = new Options(); options.setTo(targetEPR); options.setTransportInProtocol(Constants.TRANSPORT_HTTP); ServiceClient sender = new ServiceClient(configContext, null); sender.setOptions(options); OMElement result = sender.sendReceive(payload); String response = result.getText(); System.out.println("There are "+response+ " classifieds at the moment."); } catch (Exception e) { System.out.println(e.toString()); } }} |
首先,Frances 创建一个新 ConfigurationContext
,这将允许 ServiceClient
查询新配置。但为了使其实际发生,她将需要更改自己调用实际客户机类的方式。
为了客户机恰当地调用 Rampart 模块,它需要知道在何处能找到配置文件以及任何其他存储库项目(如 *.mar 文件本身)。为此,Frances 向脚本添加了更多的细节,以设置类路径和运行客户机类,如清单 18 中所示。
清单 18. 调用类
echo offSET CLASSPATH=C:/SW/axis2/lib/XmlSchema-1.0.2.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/axiom-api-1.0.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/axiom-impl-1.0.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/axis2-kernel-1.0.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/commons-codec-1.3.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/commons-httpclient-3.0.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/commons-logging-1.0.4.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/ geronimo-spec-activation-1.0.2-rc4.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/ geronimo-spec-javamail-1.3.1-rc5.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/jaxen-1.1-beta-8.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/log4j-1.2.13.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/neethi-1.0.1.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/stax-api-1.0.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/wsdl4j-1.5.2.jarSET CLASSPATH=%CLASSPATH%;C:/SW/axis2/lib/wstx-asl-2.9.3.jarSET CLASSPATH=%CLASSPATH%;C:/sw/ClassifiedClient/lib/wss4j-1.5.0.jarSET CLASSPATH=%CLASSPATH%;C:/sw/ClassifiedClient/lib/xmlsec-1.3.0.jarSET CLASSPATH=%CLASSPATH%;C:/sw/ClassifiedClient/lib/ commons-discovery-0.2.jarSET CLASSPATH=%CLASSPATH%;C:/sw/ClassifiedClient/lib/ bcprov-jdk13-132.jarSET CLASSPATH=%CLASSPATH%;C:/sw/ClassifiedClient/lib/xalan.jarSET CLASSPATH=%CLASSPATH%;.java.exe -Daxis2.xml=axis-repo/conf/axis2.xml -Daxis2.repo=axis-repo ClassifiedClient |
请注意,Frances 还添加多个来自 WSS4J 分发版本的 *.jar 文件(有关下载信息,请参见先决条件)。
现在她已经准备好再次尝试请求。
运行请求会显示一条普通 SOAP 消息,但有一个例外,如清单 19 中所示。
清单 19. 带时间戳的 SOAP 消息
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1"> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-29987161"> <wsu:Created>2006-06-19T16:22:28.578Z</wsu:Created> <wsu:Expires>2006-06-19T16:27:28.578Z</wsu:Expires> </wsu:Timestamp> </wsse:Security> </soapenv:Header> <soapenv:Body> <cms:getNumberOfArticles xmlns:cms="http://daily-moon.com/cms"> <cms:category>classifieds</cms:category> </cms:getNumberOfArticles> </soapenv:Body></soapenv:Envelope> |
现在,Frances 首次在她的消息中看到了 Security Header。在本例中,它只是一个时间戳,显示消息创建的时间。mustUnderstand 属性指示,如果服务器不知道如何处理此 Timestamp 元素,则必须拒绝此消息。
Timestamp 可能是最简单的 WS-Security 元素。它们提供了限制消息生存期的方法,防止恶意者窃听消息并*更改后再将其发出。在这种情况下,Timestamp 会在创建消息后五秒钟过期,因此如果消息存在时间超过此限制,则一定会拒绝此消息。(当然,恶意者仍然可以更改 Timestamp 值,但我们稍后将在讨论签名时处理这个问题。)
正如服务中指定的,响应也具有时间戳,如清单 20 中所示。
清单 20. 响应
<?xml version='1.0' encoding='UTF-8'?> <soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1"> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-22347273"> <wsu:Created>2006-06-19T16:22:29.281Z</wsu:Created> <wsu:Expires>2006-06-19T16:27:29.281Z</wsu:Expires> </wsu:Timestamp> <wsse11:SignatureConfirmation xmlns:wsse11="http://docs.oasis-open.org/wss/2005/xx/oasis-2005xx-wss-wssecurity-secext-1.1.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SigConf-5759024" /> </wsse:Security> </soapenv:Header> <soapenv:Body> <resp:numberOfArcticles xmlns:resp="http://daily-moon.com/cms/" xmlns:tns="http://ws.apache.org/axis2" >42</resp:numberOfArcticles> </soapenv:Body> </soapenv:Envelope> |
按指定的情况,响应包括时间戳,但也表明信息已经过处理和验证(尽管 Frances 尚未进行相应的配置来验证这两个方面)。
请注意,在这两种情况下,实际消息都保持原样。
对消息进行签名
现在,Frances 已向传出和传入消息添加了时间戳,但并没有防止直接更改其值并重新传输的机制。为了解决此问题(以及由于信息被更改而导致的任何其他问题),Frances 需要对其消息进行签名。
正如您之前已经了解到的,对消息签名涉及到以已知的方式创建已加密的数据的一个版本,以便能通过对其进行解密来提供能够与原始值进行比较的值。例如,假定 Frances 希望对消息中的 Timestamp 元素进行数字签名,以便服务器能够验证其没有被篡改或以某种方式盗用。
为此,她将制订一个将进行若干步骤的流程。
首先,她将对消息(或消息的一部分)进行签名。为此,她将使用 axis2.xml 文件来指定某个用户作为签名者。Axis2 将接受此信息,并使用其进行两个操作。首先,它将用户别名提供给“回调类”,此类将返回该用户的密码。获得了此密码后,Axis2 将随后从我们前面创建的密钥存储库检索用户的私钥。
通过使用此私钥,Axis2 将对消息的相关部分进行加密(或签名),并将签名添加到消息中。它将随后发送此消息。当服务接收到消息时,它会进行几乎完全相同的工作;它将访问密钥存储库来获取该用户的公钥,然后对签名进行验证。
请注意,如果服务要将经过签名的内容发回,在返回过程中,相应的角色将反过来。
让我们了解一下如何完成这些工作。
首先是创建 callback
类。在实际的情况中,此类可能访问 LDAP 目录或使用其他方法将用户名与密码关联,但 Frances 首先要进行概念验证,因此她将创建简单的 callback
类,在其中返回任意值,如清单 21 中所示。
清单 21. PWCallback.java,
callback
类
import org.apache.ws.security.WSPasswordCallback;import javax.security.auth.callback.Callback;import javax.security.auth.callback.CallbackHandler;import javax.security.auth.callback.UnsupportedCallbackException;import java.io.IOException;public class PWCallback implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof WSPasswordCallback) { WSPasswordCallback pc=(WSPasswordCallback)callbacks[i]; if (pc.getIdentifer().equals("gene")) { pc.setPassword("mypassword"); } else if (pc.getIdentifer().equals("frances")) { pc.setPassword("francespassword"); } else { throw new UnsupportedCallbackException( callbacks[i], "Unknown user"); } } else { throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } }} |
该类在 handle()
方法中进行的工作并不重要;重要的是它要么在 WSPasswordCallback
对象上设置密码,要么引发异常。
接下来,Frances 必须找到一个方法来告知 Axis2 在何处寻找密钥存储库以及哪个类应实际执行所有这些加密操作。为此,她创建了属性文件 security.properties,如清单 22 中所示:
清单 22. security.properties 文件
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlinorg.apache.ws.security.crypto.merlin.keystore.type=jksorg.apache.ws.security.crypto.merlin.keystore.password=mykeystorepasswordorg.apache.ws.security.crypto.merlin.file=mykeys.jks |
指定了提供者后,属性文件将定义存储库的类型(在本例中为随 Java 提供的专用格式 jks)、密钥存储库的密码以及实际密钥存储库文件的名称(为了简单起见,她将其放入了与客户机类文件相同的目录中)。
现在她需要根据自己对传出消息的设想设置 axis2.xml 文件,如清单 23 中所示。
清单 23. 带签名的 axis2.xml 文件
<axisconfig name="AxisJava2.0"> <!-- Engage the security module --> <module ref="rampart"/> <parameter name="OutflowSecurity"> <action> <items>Signature</items> <user>gene</user> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> <signatureKeyIdentifier>SKIKeyIdentifier</signatureKeyIdentifier> <signatureParts>{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body</signatureParts> </action> </parameter><!-- <parameter name="InflowSecurity"> <action> <items>Timestamp</items> </action> </parameter>-->... |
在本例中,它没有告知客户机添加时间戳,而是让其添加一个签名。看到此信息后,客户机将查看 signaturePropFile
中指定的文件,并使用其中找到的信息——以及 passwordCallbackClass
——来获取 gene 用户的密码。从此处,它将获取 gene 的密钥,并进行对 signatureParts
元素中指定的消息部分进行签名。在本例中即 Body 所表示的元素(而不是仅此元素的内容),此元素属于 http://schemas.xmlsoap.org/soap/envelope/ 命名空间的一部分。
您可以对消息的任意部分进行签名。例如,人们经常对 Timestamp 进行签名(如果有)。
那么,所有这些更改对客户机产生的消息有何影响呢?您可以在清单 24 中看到所得到的请求。
清单 24. 经过签名的请求
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <wsse:Security xmlns:wsse="..." soapenv:mustUnderstand="1"> <ds:Signature xmlns:ds="..." Id="Signature-8789796"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> <ds:SignatureMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <ds:Reference URI="#id-17764792"> <ds:Transforms> <ds:Transform Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> </ds:Transforms> <ds:DigestMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#sha1" /> <ds:DigestValue >wg+9KsR6BVBiO/hakJJwMdtU7+I=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>hom9Enzu3yHBuaF...</ds:SignatureValue> <ds:KeyInfo Id="KeyId-19475750"> <wsse:SecurityTokenReference xmlns:wsu=".." wsu:Id="STRId-31156635"> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">CuJdE1B2dUFd1dkLZSzQ5vj6MYg=</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> </wsse:Security> </soapenv:Header> <soapenv:Body xmlns:wsu="..." wsu:Id="id-17764792"> <cms:getNumberOfArticles xmlns:cms="http://daily-moon.com/cms"> <cms:category>classifieds</cms:category> </cms:getNumberOfArticles> </soapenv:Body></soapenv:Envelope> |
我从中剔除了命名空间值,以提高其可读性;接下来我们将对其详细分析。Security
元素包含所有安全性信息,当然,首先是签名。签名中首先是实际被签名的信息。向下跳一点,会看到 Reference
元素包含 URI
属性,此属性将向后引用 Body 的 id
属性,即 Frances 让客户机进行签名的部分。因此,服务可从此了解实际对什么信息进行了签名。
后退一点,对内容签名的第一步是对其进行规范化(移除无关文本节点等),但实际有两种方法可用于进行此工作,即包含法和排除法,二者的区别在于每个方法如何处理命名空间声明。因此,CanonicalizationMethod
元素指定签名使用哪个方法。
我们再看看 SignatureMethod
,顾名思义,其内容指定用于对相关数据进行签名的方法。
Reference
元素包括有关为了获得要签名的最终内容所需要进行的所有步骤的信息,以便验证过程能够进行相同的步骤并获得相同的SignatureValue
。
KeyInfo
提供用于对数据进行签名的实际密钥或对此密钥的引用。
现在客户机将创建签名,但服务并不知道如何处理此签名。Frances 将要进行此更改工作。
实际上,服务需要执行很多与客户机相同的步骤;它需要能够在密钥存储库中找到密码和密钥,因此 Frances 首先向 CMSService.aar 文件添加 mykeys.jks、PWCallback.class 和 security.properties 文件。并不一定要使用与客户机相同的类和密钥存储库,但在本例中,为了方便起见,她使用了相同的类和密钥存储库。
她还向 Axis2 应用程序的 lib 目录添加了 bcprov-jdk13-132.jar、wss4j-1.5.0.jar 和 xmlsec-1.3.0.jar。(这些文件是随 WSS4J 分发版本一起提供的。)
Frances 随后必须设置服务定义(位于 services.xml 中),以便识别传入的已签名数据。她通过进行清单 25 中所示的更改实现了此任务。
清单 25. 告知服务将接收经过签名的数据
<service name="CMSService"> <description> This is a sample Web Service for the newspaper's Content Managment System. </description> <parameter name="ServiceClass" locked="false">CMSService</parameter> <parameter name="InflowSecurity"> <action> <items>Signature</items> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> </action> </parameter> <parameter name="OutflowSecurity"> <action> <items>Timestamp</items> </action> </parameter> <operation name="getNumberOfArticles"> <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/> </operation></service> |
这些更改足以让服务知道预期将接受什么数据以及如何处理这些经过签名的数据。
对应的响应是一个包括时间戳的文档,与 OutFlowSecurity
参数指定的相符,但还包括有关签名验证的信息,如清单 26 中所示。
清单 26. 响应
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <wsse:Security xmlns:wsse="..." soapenv:mustUnderstand="1"> <wsu:Timestamp xmlns:wsu="..." wsu:Id="Timestamp-27995990"> <wsu:Created>2006-06-19T23:56:55.214Z</wsu:Created> <wsu:Expires>2006-06-20T00:01:55.214Z</wsu:Expires> </wsu:Timestamp> <wsse11:SignatureConfirmation xmlns:wsse11="..." xmlns:wsu="..." Value="hom9Enzu3yHBuaigFl26b6A+5hy..." wsu:Id="SigConf-25877728" /> </wsse:Security> </soapenv:Header> <soapenv:Body> <resp:numberOfArcticles xmlns:resp="http://daily-moon.com/cms/" xmlns:tns="http://ws.apache.org/axis2">42</resp:numberOfArcticles> </soapenv:Body></soapenv:Envelope> |
将加密添加到其中
Frances 对于使用签名进行消息验证的过程非常满意,但仍然没有办法对信息进行遮蔽,以防止竞争者和其他人阅读它。为此,她将必须将加密机制添加到应用程序中。
这次她首先处理服务。她已经添加了应用程序所需的所有其他类,因此她只需要更改 services.xml 文件即可,如清单 27 中所示。
清单 27. 将加密机制添加到服务中
<service name="CMSService"> <description> This is a sample Web Service for the newspaper's Content Managment System. </description> <parameter name="ServiceClass" locked="false">CMSService</parameter> <parameter name="InflowSecurity"> <action> <items>Timestamp Signature Encrypt</items> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> </action> </parameter> <operation name="getNumberOfArticles"> <messageReceiver class= "org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/> </operation></service> |
此处 Frances 告知服务传入的消息将具有一个添加的时间戳,它们已签名,并且已加密——按照此顺序。
现在她只需要告知客户机进行此工作即可。
由于已经打好了基础,将加密机制添加到客户机的过程也非常简单,仅涉及到对 axis2.xml 文件进行一个简单的更改即可,如清单 28 中所示。
清单 28. 将加密机制添加到客户机
<axisconfig name="AxisJava2.0"> <!-- Engage the security module --> <module ref="rampart"/> <parameter name="OutflowSecurity"> <action> <items>Timestamp Signature Encrypt</items> <user>gene</user> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> <signatureKeyIdentifier >SKIKeyIdentifier</signatureKeyIdentifier> <encryptionKeyIdentifier >SKIKeyIdentifier</encryptionKeyIdentifier> <encryptionUser>frances</encryptionUser> <signatureParts>{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body</signatureParts> <optimizeParts>//xenc:EncryptedData/xenc:CipherData/xenc:CipherValue</optimizeParts> </action> </parameter> <!-- ================================================= --> <!-- Parameters --> <!-- ================================================= --> <parameter name="hotdeployment" locked="false">true</parameter>... |
请注意,Francis 为加密指定了不同的用户,而不是用于进行数字签名的用户。并非必须这样做,但可以采用这样的方式。另请注意,添加了 optimizeParts
元素,以指定 Axis2 应如何在消息中表示经过加密的数据。
接下来让我们看看其工作情况。
一切就绪后,消息将会变得有些复杂,如清单 29 中所示。
清单 29. 经过签名、封装和加密的请求
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <soapenv:Header> <wsse:Security xmlns:wsse="..." soapenv:mustUnderstand="1"> <xenc:EncryptedKey Id="EncKeyId-229902"> <xenc:EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <ds:KeyInfo xmlns:ds="..."> <wsse:SecurityTokenReference> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">Xeg55vRyK3ZhAEhEf+YT0z986L0=</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> <xenc:CipherData> <xenc:CipherValue>PpAOXj5P0W8ukm...</xenc:CipherValue> </xenc:CipherData> <xenc:ReferenceList> <xenc:DataReference URI="#EncDataId-30957433" /> </xenc:ReferenceList> </xenc:EncryptedKey> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-17764792"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> <ds:SignatureMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <ds:Reference URI="#id-30957433"> <ds:Transforms> <ds:Transform Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> </ds:Transforms> <ds:DigestMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#sha1" /> <ds:DigestValue>+ECkM6R4GQ7AQ=...</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <SignatureValue>DIeP5AxVmfw...</ds:SignatureValue> <ds:KeyInfo Id="KeyId-16675983"> <wsse:SecurityTokenReference xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STRId-21866740"> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">CuJdE1B2dUFd1dkLZSzQ5vj6MYg=</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-13665843"> <wsu:Created>2006-06-20T00:46:58.263Z</wsu:Created> <wsu:Expires>2006-06-20T00:51:58.263Z</wsu:Expires> </wsu:Timestamp> </wsse:Security> </soapenv:Header> <soapenv:Body xmlns:wsu="..." wsu:Id="id-30957433"> <xenc:EncryptedData Id="EncDataId-30957433" Type= "http://www.w3.org/2001/04/xmlenc#Content"> <xenc:EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#aes128-cbc" /> <xenc:CipherData> <xenc:CipherValue>DZ3vWPtabb5vBpZMlEYLPjFc8r2DMJ...fSjXpBFa7gybNA==</xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedData> </soapenv:Body></soapenv:Envelope> |
首先看下面,请注意实际请求并不会出现,而 Axis2 将其替换为了一个 EncryptedData
元素,其中包括有关数据如何加密的信息以及实际的经过加密的数据(在 CypherData
和 CypherValue
元素中)。
数据是使用共享密钥加密的,这意味着消息中必须包含该密钥,以便能对其进行解密。共享密钥已使用接收方的公钥加密,嵌入在EncryptedKey
元素的 Header 中。此密钥也包括一个 ReferenceList
,而后者包括了一个 DataReference
,指回到使用此密钥进行加密的数据。
因此,在另一方面,接收者(在此情况下为服务器)将接收消息,使用自己的私钥对共享密钥进行解密,然后使用共享密钥对消息的主体进行解密。
完成了一切工作后,响应又是什么样呢?
响应实际上相当简单,因为 Frances 并未在服务器上设置任何 OutFlowSecurity
,如清单 30 中所示。
清单 30. 响应
<?xml version='1.0' encoding='UTF-8'?> <soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header /> <soapenv:Body> <resp:numberOfArcticles xmlns:resp= "http://daily-moon.com/cms/" xmlns:tns= "http://ws.apache.org/axis2" >42</resp:numberOfArcticles> </soapenv:Body> </soapenv:Envelope> |
InFlowSecurity
中包含至少一个时间戳。因此,尽管进行了所有这些工作,除非服务器和客户机都生成和期望相同的安全性方法,否则请求将仍然失败。-
组合
此时 Frances 和 Gene 仅获得了一些独立的示例,因此他们决定将所有内容都放入到单个实现中,以向 Rudy 进行演示。
Rudy 希望服务仅接收来自授权个人(或实体)的请求,而且他不希望竞争者能够阅读响应的内容。为此,Gene 和 Frances 进行了以下的工作:
- 客户机将添加一个时间戳,以供使用经过认可的用户的私钥进行签名时使用。这将处理授权访问的问题,防止对消息进行窃听和重发。
- 服务器会添加时间戳,但还将对响应进行加密,以防止窃听者看到发送回去的数据。
让我们看看这一切将如何工作。
在服务端,Gene 按照清单 31 所示对 services.xml 文件进行设置。
清单 31. 最终的 services.xml 文件
<service name="CMSService"> <description> This is a sample Web Service for the newspaper's Content Managment System. </description> <parameter name="ServiceClass" locked="false">CMSService</parameter> <parameter name="InflowSecurity"> <action> <items>Timestamp Signature</items> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> </action> </parameter> <parameter name="OutflowSecurity"> <action> <items>Timestamp Signature Encrypt</items> <user>gene</user> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> <signatureKeyIdentifier >SKIKeyIdentifier</signatureKeyIdentifier> <encryptionKeyIdentifier >SKIKeyIdentifier</encryptionKeyIdentifier> <encryptionUser>frances</encryptionUser> <signatureParts> {Element}{http://schemas.xmlsoap.org/soap/envelope/}Body </signatureParts> <optimizeParts> //xenc:EncryptedData/xenc:CipherValue/xenc:CipherData </optimizeParts> </action> </parameter> <operation name="getNumberOfArticles"> <messageReceiver class= "org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/> </operation></service> |
InflowSecurity
是服务器预期接收的内容(添加了时间戳并随后进行了签名的消息),而 OutflowSecurity
是它将发送回客户机的内容(添加了时间戳并已进行签名且数据已加密的消息)。
在客户端,Frances 将设置反向过程,如清单 32 中所示。
清单 32. 最终的 axis2.xml 文件
<axisconfig name="AxisJava2.0"> <!-- Engage the security module --> <module ref="rampart"/> <parameter name="OutflowSecurity"> <action> <items>Timestamp Signature</items> <user>gene</user> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> <signatureKeyIdentifier >SKIKeyIdentifier</signatureKeyIdentifier> <signatureParts> {Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp</signatureParts> </action> </parameter> <parameter name="InflowSecurity"> <action> <items>Timestamp Signature Encrypt</items> <user>gene</user> <passwordCallbackClass>PWCallback</passwordCallbackClass> <signaturePropFile>security.properties</signaturePropFile> <signatureKeyIdentifier >SKIKeyIdentifier</signatureKeyIdentifier> <encryptionKeyIdentifier >SKIKeyIdentifier</encryptionKeyIdentifier> <encryptionUser>frances</encryptionUser> <signatureParts> {Element}{http://schemas.xmlsoap.org/soap/envelope/}Body </signatureParts> <optimizeParts> //xenc:EncryptedData/xenc:CipherValue/xenc:CipherData </optimizeParts> </action> </parameter> <!-- ================================================= --> <!-- Parameters --> <!-- ================================================= --> <parameter name="hotdeployment" locked="false">true</parameter>... |
现在客户机将发送添加了时间戳并已签名的消息,且坚持任何响应都必须带时间戳,且已签名和加密。
让我们看看这对实际消息的影响。
清单 33 显示了最终的请求。
清单 33. 最终的请求
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-5525185"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> <ds:SignatureMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <ds:Reference URI="#Timestamp-1741620"> <ds:Transforms> <ds:Transform Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> </ds:Transforms> <ds:DigestMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#sha1" /> <ds:DigestValue> TQSR9wUuJ7rJi582TsbNjiAUqZI= </ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>aRI5mvvvXZusAB/5cKCx/fOcW+CDjdk1F3IcllObVcEOWws9/mV4X2kWEX3hhwK7koX5jMPpl7AtLSbEh8UQGCa8yBua++yveprFl020ToVtePVOcWsBLM+9VHu9bJbhvaaps43RiUkym6xvVU/yL3eKTbhdhB/RQDI3kylXdas=</ds:SignatureValue> <ds:KeyInfo Id="KeyId-26644003"> <wsse:SecurityTokenReference xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STRId-26174005"> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">CuJdE1B2dUFd1dkLZSzQ5vj6MYg=</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-1741620"> <wsu:Created>2006-06-22T11:34:02.453Z</wsu:Created> <wsu:Expires>2006-06-22T11:39:02.453Z</wsu:Expires> </wsu:Timestamp> </wsse:Security> </soapenv:Header> <soapenv:Body> <cms:getNumberOfArticles xmlns:cms="http://daily-moon.com/cms"> <cms:category>classifieds</cms:category> </cms:getNumberOfArticles> </soapenv:Body> </soapenv:Envelope> |
清单 34 显示了最终的响应。
清单 34. 最终的响应
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <soapenv:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1"> <xenc:EncryptedKey Id="EncKeyId-28585008"> <xenc:EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <wsse:SecurityTokenReference> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">Xeg55vRyK3ZhAEhEf+YT0z986L0=</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> <xenc:CipherData> <xenc:CipherValue>NSkylkASezzHSp37izSN3xnxf6v/zwN3C70uU2nUTNk4a9xYxhNcgiVQuS2/Tm3/x3Jm1d9rj2V8x1uqlKmi89MFifN34SDxaDTMBFzhfRv4CmQSITEFjY1ySVDvMb7WZszGDhVIGYkjcDkoK+SfWdxyuaUdNUbPgEihSnFVRXs=</xenc:CipherValue> </xenc:CipherData> <xenc:ReferenceList> <xenc:DataReference URI="#EncDataId-19400027" /> </xenc:ReferenceList> </xenc:EncryptedKey> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-17174249"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> <ds:SignatureMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <ds:Reference URI="#id-19400027"> <ds:Transforms> <ds:Transform Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> </ds:Transforms> <ds:DigestMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#sha1" /> <ds:DigestValue>HfdufYEGpvPpfz2+HWKui4npV9s=</ds:DigestValue> </ds:Reference> <ds:Reference URI="#SigConf-17122634"> <ds:Transforms> <ds:Transform Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> </ds:Transforms> <ds:DigestMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#sha1" /> <ds:DigestValue>e88WWqudpvW69wN23fgZjQ9ZAio=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>aZLon//vwkw2G2Jxcligxod/CgxMjwtlefZihoyUz5FpgSY6RUoI5vuHX2unrWV+EVA2vWdtz/Iyq+RS7j4QtE2XTYovxdyiZPbKXNdFKHyAkpDr0aDLG9rSjyFVcTrUKgAY06t10zi13Daq95nDMH+wAJCYUO0Vor/u0V9Iv7I=</ds:SignatureValue> <ds:KeyInfo Id="KeyId-22768665"> <wsse:SecurityTokenReference xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STRId-18220809"> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">CuJdE1B2dUFd1dkLZSzQ5vj6MYg=</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-6400133"> <wsu:Created>2006-06-22T11:34:04.062Z</wsu:Created> <wsu:Expires>2006-06-22T11:39:04.062Z</wsu:Expires> </wsu:Timestamp> <wsse11:SignatureConfirmation xmlns:wsse11="http://docs.oasis-open.org/wss/2005/xx/oasis-2005xx-wss-wssecurity-secext-1.1.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Value="aRI5mvvvXZusAB/5cKCx/fOcW+CDjdk1F3IcllObVcEOWws9/mV4X2kWEX3hhwK7koX5jMPpl7AtLSbEh8UQGCa8yBua++yveprFl020ToVtePVOcWsBLM+9VHu9bJbhvaaps43RiUkym6xvVU/yL3eKTbhdhB/RQDI3kylXdas=" wsu:Id="SigConf-17122634" /> </wsse:Security> </soapenv:Header> <soapenv:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="id-19400027"> <xenc:EncryptedData Id="EncDataId-19400027" Type="http://www.w3.org/2001/04/xmlenc#Content"> <xenc:EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#aes128-cbc" /> <xenc:CipherData> <xenc:CipherValue>f6uWHGsYmGwHuno2j4H7a4qCMhPLTlCIg40pKLciESBzeCT8rvyl+qHXsFkZJq2m4uj9TEFtRX6efQ5MHBEJozMgI03LSVanh6MmHgt5oilIJClWcQifEx0Azeo3KWnQKSc9lg0ywhKJH+JVBsPSP7E19jZAsR77wUEBBIprxs5W597C/mJh38iXSncwWccE7OCckf1x34FCfKHSqn46MCohZWiPZRjSmAI5dGFMKwttzpmsmXrLHLVrsjm4w9onis+Xr5gbi3Gcx6P0F2ZJGLBb9bkGh/IvjYutgzRD7zhyRZxUmM/oZTVsJJ7dA9YOED5l1C64f4yuqR6TtuVw3gIiuspxWafKwlJuuD0/9m6Ri4AvQuOVEioz45MM5FBCQU+0LFceSlEFFKhN9yLUI9hgLsCYRzc8eedPAhZDjJEDHec5M9LZ0C07sKu7Cvnrjiino53xZmk5uQHs4JlNoA==</xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedData> </soapenv:Body></soapenv:Envelope> |
总结
为了让 Web 服务真正能在企业环境中使用,需要具有相应的安全功能。通过结合使用 XML Signature 和 XML Encryption 等技术,并提供表示信息的标准方式,WS-Security 可保护传入和传出 SOAP 消息不受多种不同的安全威胁的危害。
通过要求使用数字签名,可将访问限制为经过授权的个人或组织,并验证信息没有在传输过程中被更改。通过包含加密机制,可以防止数据被非目标接收方看到(或至少不能被其理解)。通过添加时间戳(并对其进行签名),可以防止消息被捕获和重发。
在本教程中,Daily Moon 的员工对在本系列教程前面部分创建的 Web 服务进行了保护。接下来,在第 5 部分中,我们将了解如何将安全策略应用到服务上。