内容


Java Web 服务

WS-Security 的细粒度使用

在操作或消息级别应用 WS-Security

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Java Web 服务

敬请期待该系列的后续内容。

此内容是该系列的一部分:Java Web 服务

敬请期待该系列的后续内容。

在简单 Web 服务环境中,客户机直接连接到服务器,而服务器直接对请求执行所有必需的处理。正如本系列 上一篇文章 所述,使用 SSL 提供保护的连接可以为这类环境中的大部分应用提供出色的安全性。但是,更加复杂的环境变得越来越普遍,其中涉及到使用多层服务器处理请求。在许多企业环境中日益流行的服务编排的完整理念就是以这种方法为基础的,这与面向服务架构(SOA)的概念相同。在这些类型的环境中,必须实现更强大的 WS-Security 替代方案。

正如上一期文章所述,WS-Security 带来了沉重的性能代价。降低成本的方法之一就是针对由服务定义的各个操作甚至是消息设置合适的 WS-SecurityPolicy,而不是将单个 WS-Security 策略应用到整个服务。WS-Security 的细粒度使用要求考虑更多的内容,而不是使用一成不变的方法,但是如果得到正确应用的话,那么就可以减少常用操作的性能开销,同时不会减弱需要 WS-Security 的操作的安全性。

定义策略

本文使用的样例策略与 “Axis2 WS-Security 基础” 和 “Axis2 WS-Security 签名和加密” 中的策略相同 — 一个简单的库管理服务。(参见 下载 小节,获得本文的完整源代码)。这个服务定义了三种操作:

  • getBook,检索由 International Standard Book Number (ISBN) 标识的特定图书的细节。
  • getBooksByType,检索某一类型的所有图书的细节。
  • addBook,向库中添加一本新书。

为了向安全用例中添加一些有趣的变化,本文作出以下假设:

  • getBook 操作可以安全地公开给任何人(未应用安全性)。
  • getBooksByType 需要授权(因此要使用 UsernameToken)。
  • addBook 操作需要一个审计跟踪,以跟踪是谁添加了图书(通过对请求消息进行签名实现)。

在早期文章中,您已经了解了如何配置 Axis2/Rampart:将一个 WS-SecurityPolicy 文档连接到 org.apache.axis2.client.ServiceClient 实例(在客户机端),或者将策略文档嵌入到 services.xml 服务配置(在服务器端)。这个方法可以奏效,并且可用于测试,但是对于生产应用来说,最好通过将 WS-SecurityPolicy 内嵌到 WSDL 文档将其直接关联到服务定义。WS-Policy 和 WS-SecurityPolicy 旨在为这种嵌入提供支持,并且使用来自 <wsdl:binding><wsdl:binding>/<wsdl:operation><wsdl:binding>/<wsdl:operation>/<wsdl:message> 定义的引用标识将被应用到绑定、操作或消息的相应策略。Axis2 1.4.1 实现对内嵌在 WSDL 中的策略的初始处理,而这个实现在当前的 Axis2 1.5 发行版代码中得到了改进。为了演示策略在 WSDL 中的使用,本文结合使用了 Axis2 1.5 发行版代码和尚未发行的最新 Rampart 代码(后者最终会作为 Rampart 1.5 发布)。

清单 1 展示了示例应用程序的 WSDL,其中添加了策略,并在相应的位置中引用。(清单 1 针对长度和宽度进行了编辑;完整的 WSDL 可以从 代码下载 中的 library.wsdl 文件获得)。每个策略定义了一个 Id 值,该值随后从相应的操作(针对 UsernameToken 策略)或消息(针对签名策略)中引用,以粗体显示所有策略。

清单 1. 具有细粒度安全策略的 WSDL
<wsdl:definitions targetNamespace="http://ws.sosnoski.com/library/wsdl"
    xmlns:wns="http://ws.sosnoski.com/library/wsdl"
    xmlns:tns="http://ws.sosnoski.com/library/types"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">

  <!-- Policy for signing message, with certificate from client included in each
    message to server -->
  <wsp:Policy wsu:Id="SignOnly" xmlns:wsu=
      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <sp:AsymmetricBinding
            xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
          <wsp:Policy>
            <sp:InitiatorToken>
              <wsp:Policy>
                <sp:X509Token sp:IncludeToken=".../IncludeToken/AlwaysToRecipient"/>
              </wsp:Policy>
            </sp:InitiatorToken>
            ...
          </wsp:Policy>
        </sp:AsymmetricBinding>
        <sp:SignedParts
            xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
          <sp:Body/>
        </sp:SignedParts>

      </wsp:All>
    </wsp:ExactlyOne>
  </wsp:Policy>

  <!-- Policy for UsernameToken with plaintext password, sent from client to
    server only -->
  <wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <sp:SupportingTokens
            xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
          <wsp:Policy>
            <sp:UsernameToken sp:IncludeToken=".../IncludeToken/AlwaysToRecipient"/>
          </wsp:Policy>
        </sp:SupportingTokens>
      </wsp:All>
    </wsp:ExactlyOne>
  </wsp:Policy>
  ...
  <wsdl:binding name="LibrarySoapBinding" type="wns:Library">

    <wsdlsoap:binding style="document"
      transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getBook">
      <wsdlsoap:operation soapAction="urn:getBook"/>
      <wsdl:input name="getBookRequest">
        <wsdlsoap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getBookResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>

    <wsdl:operation name="getBooksByType">
      <wsp:PolicyReference
          xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
          URI="#UsernameToken"/>
      <wsdlsoap:operation soapAction="urn:getBooksByType"/>
      <wsdl:input name="getBooksByTypeRequest">
        <wsdlsoap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getBooksByTypeResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>

    <wsdl:operation name="addBook">
      <wsdlsoap:operation soapAction="urn:addBook"/>
      <wsdl:input name="addBookRequest">
        <wsp:PolicyReference
            xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
            URI="#SignOnly"/>
        <wsdlsoap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="addBookResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>

  </wsdl:binding>
  <wsdl:service name="library-granular">
    ...
  </wsdl:service>
</wsdl:definitions>

清单 1 中的策略和 WSDL 均来自早期文章,不过没有按照本文的方式事先合并。但是,您会发现策略中有一处显著的不同:早期的版本都包括了 Rampart 配置信息,这些信息特定于客户机或服务器。现在策略被嵌入到 WSDL 中,因此不适合直接包含 Rampart 配置。(您需要编辑 WSDL 以包含客户机 Rampart 配置信息,并在每次发生更改时重新生成代码,而在服务器端,Rampart 配置将被公开给访问 WSDL 的任何人)。因此示例代码将单独设置配置信息。为此,对包含的 Rampart 配置使用了之前用于策略的相同技巧的不同变体。

客户端使用

不管是否包含 WS-Policy,从用户的角度来看,从 WSDL 中生成代码的效果是相同的。如果查看由包含 WS-Policy 的 WSDL 生成的客户机存根的内部,将发现策略在构建时被直接连接到服务描述的组件中,但是这被隐藏在实现的内部,不会影响到客户机代码使用的接口方法。

要在客户机中利用 WS-SecurityPolicy 配置,您一定 需要在客户机代码中采取一些操作。您至少需要在与您的存根实例相关联的 org.apache.axis2.client.ServiceClient 上使用 Rampart 模块。这一步是必须的,即使您在 WSDL 中包含了 Rampart 配置信息。不幸的是,在当前的代码中,似乎没有任何方法可以在操作或消息级别使用 Rampart,因此目前与 Axis2 客户机结合使用时,细粒度 WS-Security 的一部分好处就丢失了。

如果将 Rampart 配置与 WSDL 分离,正如建议的那样,您也需要对服务描述应用这些配置。清单 2 展示了用于本样例应用程序的客户机代码。它调用 applyPolicy() 方法来将包含 Rampart 配置的策略添加到服务器定义。

清单 2. 配置客户端操作
// create the client stub
String target = args[0] + "://" + args[1] + ":" + args[2] + args[3];
System.out.println("Connecting to " + target);
LibraryGranularStub stub = new LibraryGranularStub(target);

// configure and engage rampart module
ServiceClient client = stub._getServiceClient();
client.getAxisService().applyPolicy(loadPolicy("rampart-client-policy.xml"));
client.engageModule("rampart");

// set the username and password for requests which use them
Options options = client.getOptions();
options.setUserName("libuser");
options.setPassword("books");

清单 2 中的代码根据 ServiceClient 选项设置用户名和密码,意味着这些是针对使用该服务的所有操作定义的,即使它们只用于单个操作。与将 Rampart 模块用于所有操作不同的是,以这种方式设置用户名和密码是无害的 — 这些值只有在 Rampart 需要用之构建 UsernameToken 时使用,其他情况下将会忽略。

清单 3 展示了包含有 Rampart 配置的策略文档。这里使用了与 “Axis2 WS-Security 签名和加密” 相同的策略,现在被提取到一个独立的策略文档中。

清单 3. Rampart 客户机配置策略
<wsp:Policy xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
  <wsp:ExactlyOne>
    <wsp:All>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
        <ramp:user>clientkey</ramp:user>
        <ramp:passwordCallbackClass
          >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
        
        <ramp:signatureCrypto>
          <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
            <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
              >JKS</ramp:property>
            <ramp:property name="org.apache.ws.security.crypto.merlin.file"
              >client.keystore</ramp:property>
            <ramp:property
              name="org.apache.ws.security.crypto.merlin.keystore.password"
              >nosecret</ramp:property>
          </ramp:crypto>
        </ramp:signatureCrypto>
        
      </ramp:RampartConfig>
    
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

代码下载中包含的 Ant build.xml 将处理从 WSDL 生成客户机和服务器代码、编译代码,以及生成服务 AAR 文件。它还会将 Rampart 配置策略(即下载中的 rampart-client-policy.xml,位于根目录)复制到客户机类路径,同时执行一些服务器端策略处理,下节将进行讨论。

服务器端使用

如果希望将服务器端 Rampart 配置与 WSDL 分离(通常是一个很不错的主意!),需要将它包含到 services.xml 文件中。在早期文章中我们执行了这个操作,以获得包括 Rampart 配置的完整 WS-SecurityPolicy 配置,并作为一个整体应用到服务。这一次,Rampart 配置策略是被添加到 services.xml 的唯一一个部分,并且是在操作级别完成的。

清单 4 展示了包含有 Rampart 配置的策略文档。与客户端的对应策略相同,这里使用了与 “Axis2 WS-Security 签名和加密” 相同的策略,现在被提取到一个独立的策略文档中。

清单 4. Rampart 服务器配置策略
<wsp:Policy xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
  <wsp:ExactlyOne>
    <wsp:All>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
        <ramp:user>serverkey</ramp:user>
        <ramp:passwordCallbackClass
          >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
        
        <ramp:signatureCrypto>
          <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
            <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
              >JKS</ramp:property>
            <ramp:property name="org.apache.ws.security.crypto.merlin.file"
              >server.keystore</ramp:property>
            <ramp:property
              name="org.apache.ws.security.crypto.merlin.keystore.password"
              >nosecret</ramp:property>
          </ramp:crypto>
        </ramp:signatureCrypto>
        
      </ramp:RampartConfig>
    
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

Ant build.xml 使用了一个 PolicyTool 程序(包含在代码下载中)将 清单 4 中的策略(即下载中的 rampart-client-policy.xml,位于根目录)合并到 services.xml。修改过的 services.xml 如 清单 5 所示(针对宽度和长度进行了编辑):

清单 5. 合并后的 services.xml
<serviceGroup>
  <service name="library-granular">
    <messageReceivers>
      <messageReceiver class=
        "com.sosnoski.ws.library.adb.LibraryGranularMessageReceiverInOut"
        mep="http://www.w3.org/ns/wsdl/in-out"/>
    </messageReceivers>
    <parameter name="ServiceClass"
      >com.sosnoski.ws.library.adb.LibraryGranularImpl</parameter>
    <parameter name="useOriginalwsdl">true</parameter>
    <parameter name="modifyUserWSDLPortAddress">true</parameter>
    <operation mep="http://www.w3.org/ns/wsdl/in-out" name="getBook"
      namespace="http://ws.sosnoski.com/library/wsdl">
      <actionMapping>urn:getBook</actionMapping>
      <outputActionMapping>.../getBookResponse</outputActionMapping>
    </operation>
    <operation mep="http://www.w3.org/ns/wsdl/in-out" name="getBooksByType"
      namespace="http://ws.sosnoski.com/library/wsdl">
      <actionMapping>urn:getBooksByType</actionMapping>
      <outputActionMapping>.../getBooksByTypeResponse</outputActionMapping>

<module ref="rampart"/>
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <wsp:ExactlyOne>
  <wsp:All>

    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
    <ramp:user>serverkey</ramp:user>
    ...

    </ramp:RampartConfig>

  </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>
  </operation>
    <operation mep="http://www.w3.org/ns/wsdl/in-out" name="addBook" 
    namespace="http://ws.sosnoski.com/library/wsdl">
      <actionMapping>urn:addBook</actionMapping>
      <outputActionMapping>
        http://ws.sosnoski.com/library/wsdl/Library/addBookResponse
      </outputActionMapping>

<module ref="rampart"/>
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <wsp:ExactlyOne>
  <wsp:All>

    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> 
    <ramp:user>serverkey</ramp:user>
    ...

    </ramp:RampartConfig>

  </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>
  </operation>
  </service>
</serviceGroup>

清单 5 中,可以看到一个 Rampart 模块引用和一个服务器配置策略的副本被添加到使用 WS-Security 的两个操作的定义中,这两个操作分别为 getBooksByTypeaddBook。这些修改将服务配置为只针对这两个操作使用 Rampart,并对 Rampart 的键和证书访问使用提供的设置。

如果构建生成的 AAR 文件并部署到一个 Axis2 服务器安装中,可以在 Administration/Available Services 页面中看到这些操作的结果,在这个页面中,将列出由服务定义的每个操作使用的模块。如果运行客户机代码并监视消息交换(使用 TCPMon 之类的工具,像 “Axis2 WS-Security 基础” 中描述的那样),您会看到安全处理将按照我们的期望运行:

  • getBook 消息中不存在 WS-Security 头部
  • getBooksByType 请求消息中有一个包含 UsernameToken 的 WS-Security 头部
  • addBook 请求(而不是响应)消息中有一个包含签名的 WS-Security 头部

Axis2/Rampart 限制

Axis2 和 Rampart 目前(也就是说,使用最新的 Axis2 1.5 发行版和一个 Rampart 夜间构建版)能够处理 WSDL 中内嵌的基本 WS-SecurityPolicy 配置,包括在操作或消息级别定义的粒度策略。然而,本文尝试的一些配置无法用于 Axis2/Rampart。例如,尝试为 addBook 操作设置 UsernameToken 策略(除了对输入消息使用签名策略外),结果将导致一个明显与 Rampart 代码加密有关的错误(即使没有使用任何加密)。从这些经验以及一些问题报告中可以判断出,Axis2/Rampart WS-SecurityPolicy 处理适合用于可以反映常见用例的简单配置,但是当应用到不常见的组合时,却容易出现错误。

另一个问题是,在服务器上使用 Rampart 产生的开销始终会出现在请求和响应处理中,即使您只对其中一条消息应用 WS-Security(像 “WS-Security 的大开销” 所讨论的那样)。在客户端,情况更糟糕 — 因为在客户机上使用 Rampart 目前是一种全有或全无的主张,如果在任何 操作上使用 WS-Security,那么不得不忍受每个 操作中产生的 Rampart 开销。

最后,目前的 Axis2/Rampart WS-Policy 处理并不支持策略替代方案。替代选择是 WS-Policy 设计的一个主要特点,使客户机在访问服务时可以从两个或多个可行配置中选择要使用的配置。Axis2/Rampart 策略实现并不支持这个特性,而是忽略除第一种替代选择以外的所有选择。

结束语

通过本文和前三篇文章,Java Web 服务 系列文章介绍了 Axis2/Rampart 中的 WS-Security 和 WS-SecurityPolicy 处理的所有主要方面,包括性能问题。在未来几期文章中,您将了解其他 Java Web 服务框架如何使用 WS-Security 和 WS-SecurityPolicy,但是首先需要查看 Axis2 的其他两个方面。

对于许多组织而言,一个重要的问题就是支持 Java Architecture for XML Binding (JAXB) 2.X。JAXB 2.X 是面向 XML 数据绑定的官方 Java 标准,尽管开源替代方案提供了自己独特的优势,一些组织仍然坚持使用该标准。在下一期文章中,您将看到如何结合使用 JAXB 2.X 数据绑定和 Axis2,并将它与其他受 Axis2 支持的数据绑定选择进行比较。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, SOA and web services, Open source
ArticleID=430229
ArticleTitle=Java Web 服务: WS-Security 的细粒度使用
publish-date=09222009